terminate cleanly on SIGHUP
[claws.git] / src / ldapquery.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2003-2004 Match Grun
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * Functions necessary to define and perform LDAP queries.
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #  include "config.h"
26 #endif
27
28 #ifdef USE_LDAP
29
30 #include <glib.h>
31 #include <sys/time.h>
32 #include <string.h>
33 #include <lber.h>
34
35 #include "ldapquery.h"
36 #include "ldapctrl.h"
37 #include "mgutils.h"
38
39 #include "addritem.h"
40 #include "addrcache.h"
41
42 #include "ldapquery.h"
43
44 /*
45  * Key for thread specific data.
46  */
47 static pthread_key_t _queryThreadKey_;
48 static gboolean _queryThreadInit_ = FALSE;
49
50 /**
51  * Create new LDAP query object.
52  * \return Initialized query object.
53  */
54 LdapQuery *ldapqry_create( void ) {
55         LdapQuery *qry;
56
57         qry = g_new0( LdapQuery, 1 );
58         ADDRQUERY_TYPE(qry) = ADDRQUERY_LDAP;
59         ADDRQUERY_ID(qry) = 0;
60         ADDRQUERY_SEARCHTYPE(qry) = ADDRSEARCH_NONE;
61         ADDRQUERY_NAME(qry) = NULL;
62         ADDRQUERY_RETVAL(qry) = LDAPRC_SUCCESS;
63         ADDRQUERY_FOLDER(qry) = NULL;
64         ADDRQUERY_SEARCHVALUE(qry) = NULL;
65         qry->control = NULL;
66         qry->server = NULL;
67         qry->entriesRead = 0;
68         qry->elapsedTime = 0;
69         qry->stopFlag = FALSE;
70         qry->busyFlag = FALSE;
71         qry->agedFlag = FALSE;
72         qry->completed = FALSE;
73         qry->thread = NULL;
74         qry->callBackEntry = NULL;
75         qry->callBackEnd = NULL;
76         qry->ldap = NULL;
77         qry->data = NULL;
78
79         /* Mutex to protect stop and busy flags */
80         qry->mutexStop = g_malloc0( sizeof( pthread_mutex_t ) );
81         pthread_mutex_init( qry->mutexStop, NULL );
82         qry->mutexBusy = g_malloc0( sizeof( pthread_mutex_t ) );
83         pthread_mutex_init( qry->mutexBusy, NULL );
84
85         /* Mutex to protect critical section */
86         qry->mutexEntry = g_malloc0( sizeof( pthread_mutex_t ) );
87         pthread_mutex_init( qry->mutexEntry, NULL );
88
89         return qry;
90 }
91
92 /**
93  * Specify the reference to control data that will be used for the query. The calling
94  * module should be responsible for creating and destroying this control object.
95  * \param qry Query object.
96  * \param ctl Control object.
97  */
98 void ldapqry_set_control( LdapQuery *qry, LdapControl *ctl ) {
99         g_return_if_fail( qry != NULL );
100         qry->control = ctl;
101 }
102
103 /**
104  * Specify query name to be used.
105  * \param qry   Query object.
106  * \param value Name.
107  */
108 void ldapqry_set_name( LdapQuery* qry, const gchar *value ) {
109         ADDRQUERY_NAME(qry) = mgu_replace_string( ADDRQUERY_NAME(qry), value );
110         g_strstrip( ADDRQUERY_NAME(qry) );
111 }
112
113 /**
114  * Specify search value to be used.
115  * \param qry Query object.
116  * \param value 
117  */
118 void ldapqry_set_search_value( LdapQuery *qry, const gchar *value ) {
119         ADDRQUERY_SEARCHVALUE(qry) = mgu_replace_string( ADDRQUERY_SEARCHVALUE(qry), value );
120         g_strstrip( ADDRQUERY_SEARCHVALUE(qry) );
121 }
122
123 /**
124  * Specify error/status.
125  * \param qry   Query object.
126  * \param value Status.
127  */
128 void ldapqry_set_error_status( LdapQuery* qry, const gint value ) {
129         ADDRQUERY_RETVAL(qry) = value;
130 }
131
132 /**
133  * Specify query type.
134  * \param qry Query object.
135  * \param value Query type, either:
136  * <ul>
137  * <li><code>LDAPQUERY_NONE</code></li>
138  * <li><code>LDAPQUERY_STATIC</code></li>
139  * <li><code>LDAPQUERY_DYNAMIC</code></li>
140  * </ul>
141  */
142 /*
143 void ldapqry_set_query_type( LdapQuery* qry, const gint value ) {
144         ADDRQUERY_TYPE(qry) = value;
145 }
146 */
147
148 /**
149  * Specify search type.
150  * \param qry   Query object.
151  * \param value Type.
152  */
153 void ldapqry_set_search_type( LdapQuery *qry, const AddrSearchType value ) {
154         g_return_if_fail( qry != NULL );
155         ADDRQUERY_SEARCHTYPE(qry) = value;
156 }
157
158 /**
159  * Specify query ID.
160  * \param qry Query object.
161  * \param value ID for the query.
162  */
163 void ldapqry_set_query_id( LdapQuery* qry, const gint value ) {
164         ADDRQUERY_ID(qry) = value;
165 }
166
167 /**
168  * Specify maximum number of LDAP entries to retrieve.
169  * \param qry Query object.
170  * \param value Entries to read.
171  */
172 void ldapqry_set_entries_read( LdapQuery* qry, const gint value ) {
173         if( value > 0 ) {
174                 qry->entriesRead = value;
175         }
176         else {
177                 qry->entriesRead = 0;
178         }
179 }
180
181 /**
182  * Register a callback function that will be executed when each entry
183  * has been read and processed. When called, the function will be passed
184  * this query object and a GList of ItemEMail objects as arguments. An
185  * example of typical usage is shown below.
186  *
187  * <pre>
188  * ------------------------------------------------------------
189  * void myCallbackEntry( LdapQuery *qry, GList *listEMail ) {
190  *   GList *node;
191  *
192  *   node = listEMail;
193  *   while( node ) {
194  *     ItemEMail *email = node->data;
195  *     ... process email object ...
196  *     node = g_list_next( node );
197  *   }
198  *   g_list_free( listEMail );
199  * }
200  * ...
201  * ...
202  * ldapqry_set_callback_entry( qry, myCallbackEntry );
203  * ------------------------------------------------------------
204  * </pre>
205  *
206  * \param qry Query object.
207  * \param func Function.
208  */
209 void ldapqry_set_callback_entry( LdapQuery *qry, void *func ) {
210         pthread_mutex_lock( qry->mutexEntry );
211         qry->callBackEntry = func;
212         pthread_mutex_unlock( qry->mutexEntry );
213 }
214
215 /**
216  * Register a callback function that will be executed when the search
217  * is complete. When called, the function will be passed this query
218  * object as an argument.
219  * \param qry Query object.
220  * \param func Function.
221  */
222 void ldapqry_set_callback_end( LdapQuery *qry, void *func ) {
223         qry->callBackEnd = func;
224 }
225
226 /**
227  * Notify query to start/stop executing. This method should be called with a
228  * value if <i>TRUE</i> to terminate an existing running query.
229  *
230  * \param qry Query object.
231  * \param value Value of stop flag.
232  */
233 void ldapqry_set_stop_flag( LdapQuery *qry, const gboolean value ) {
234         g_return_if_fail( qry != NULL );
235
236         pthread_mutex_lock( qry->mutexStop );
237         qry->stopFlag = value;
238         pthread_mutex_unlock( qry->mutexStop );
239 }
240
241 /**
242  * Test value of stop flag. This method should be used to determine whether a
243  * query has stopped running.
244  * \param qry Query object.
245  * \return Value of stop flag.
246  */
247 gboolean ldapqry_get_stop_flag( LdapQuery *qry ) {
248         gboolean value;
249         g_return_if_fail( qry != NULL );
250
251         pthread_mutex_lock( qry->mutexStop );
252         value = qry->stopFlag;
253         pthread_mutex_unlock( qry->mutexStop );
254         return value;
255 }
256
257 /**
258  * Set busy flag.
259  * \param qry Query object.
260  * \param value Value of busy flag.
261  */
262 void ldapqry_set_busy_flag( LdapQuery *qry, const gboolean value ) {
263         g_return_if_fail( qry != NULL );
264
265         pthread_mutex_lock( qry->mutexBusy );
266         qry->busyFlag = value;
267         pthread_mutex_unlock( qry->mutexBusy );
268 }
269
270 /**
271  * Test value of busy flag. This method will return a value of <i>FALSE</i>
272  * when a query has completed running.
273  * \param qry Query object.
274  * \return Value of busy flag.
275  */
276 gboolean ldapqry_get_busy_flag( LdapQuery *qry ) {
277         gboolean value;
278         g_return_if_fail( qry != NULL );
279
280         pthread_mutex_lock( qry->mutexBusy );
281         value = qry->busyFlag;
282         pthread_mutex_unlock( qry->mutexBusy );
283         return value;
284 }
285
286 /**
287  * Set query aged flag.
288  * \param qry Query object.
289  * \param value Value of aged flag.
290  */
291 void ldapqry_set_aged_flag( LdapQuery *qry, const gboolean value ) {
292         g_return_if_fail( qry != NULL );
293         qry->agedFlag = value;
294 }
295
296 /**
297  * Test value of aged flag.
298  * \param qry Query object.
299  * \return <i>TRUE</i> if query has been marked as aged (and can be retired).
300  */
301 gboolean ldapqry_get_aged_flag( LdapQuery *qry ) {
302         g_return_if_fail( qry != NULL );
303         return qry->agedFlag;
304 }
305
306 /**
307  * Specify user data for query.
308  * \param qry Query object.
309  * \param value Data to set.
310  */
311 void ldapqry_set_data( LdapQuery *qry, const gpointer value ) {
312         g_return_if_fail( qry != NULL );
313         qry->data = value;
314 }
315
316 /**
317  * Retrieve user data associated with query.
318  * \param qry Query object.
319  * \return Data.
320  */
321 gpointer ldapqry_get_data( LdapQuery *qry ) {
322         g_return_if_fail( qry != NULL );
323         return qry->data;
324 }
325
326 /**
327  * Release the LDAP control data associated with the query.
328  * \param qry Query object to process.
329  */
330 void ldapqry_release_control( LdapQuery *qry ) {
331         g_return_if_fail( qry != NULL );
332         if( qry->control != NULL ) {
333                 ldapctl_free( qry->control );
334         }
335         qry->control = NULL;
336 }
337
338 /**
339  * Clear LDAP query member variables.
340  * \param qry Query object.
341  */
342 void ldapqry_clear( LdapQuery *qry ) {
343         g_return_if_fail( qry != NULL );
344
345         /* Free internal stuff */
346         g_free( ADDRQUERY_NAME(qry) );
347         g_free( ADDRQUERY_SEARCHVALUE(qry) );
348
349         /* Clear pointers and value */
350         ADDRQUERY_NAME(qry) = NULL;
351         ADDRQUERY_SEARCHVALUE(qry) = NULL;
352         ADDRQUERY_ID(qry) = 0;
353         ADDRQUERY_SEARCHTYPE(qry) = ADDRSEARCH_NONE;
354         ADDRQUERY_RETVAL(qry) = LDAPRC_SUCCESS;
355         qry->entriesRead = 0;
356         qry->elapsedTime = 0;
357         qry->stopFlag = FALSE;
358         qry->busyFlag = FALSE;
359         qry->agedFlag = FALSE;
360         qry->completed = FALSE;
361         qry->callBackEntry = NULL;
362         qry->callBackEnd = NULL;
363         qry->ldap = NULL;
364         qry->data = NULL;
365 }
366
367 /**
368  * Free up LDAP query object by releasing internal memory. Note that
369  * the thread object will be freed by the OS.
370  * \param qry Query object to process.
371  */
372 void ldapqry_free( LdapQuery *qry ) {
373         g_return_if_fail( qry != NULL );
374
375         /* Clear out internal members */
376         ADDRQUERY_TYPE(qry) = ADDRQUERY_NONE;
377         ldapqry_clear( qry );
378
379         /* Free the mutex */
380         pthread_mutex_destroy( qry->mutexStop );
381         pthread_mutex_destroy( qry->mutexBusy );
382         pthread_mutex_destroy( qry->mutexEntry );
383         g_free( qry->mutexStop );
384         g_free( qry->mutexBusy );
385         g_free( qry->mutexEntry );
386         qry->mutexEntry = NULL;
387         qry->mutexBusy = NULL;
388         qry->mutexStop = NULL;
389
390         /* Do not free folder - parent server object should free */     
391         ADDRQUERY_FOLDER(qry) = NULL;
392
393         /* Do not free thread - thread should be terminated before freeing */
394         qry->thread = NULL;
395
396         /* Do not free LDAP control - should be destroyed before freeing */
397         qry->control = NULL;
398
399         /* Now release object */
400         g_free( qry );
401 }
402
403 /**
404  * Display object to specified stream.
405  * \param qry    Query object to process.
406  * \param stream Output stream.
407  */
408 void ldapqry_print( const LdapQuery *qry, FILE *stream ) {
409         g_return_if_fail( qry != NULL );
410
411         fprintf( stream, "LdapQuery:\n" );
412         fprintf( stream, "  control?: %s\n",   qry->control ? "yes" : "no" );
413         fprintf( stream, "err/status: %d\n",   ADDRQUERY_RETVAL(qry) );
414         fprintf( stream, "query type: %d\n",   ADDRQUERY_TYPE(qry) );
415         fprintf( stream, "searchType: %d\n",   ADDRQUERY_SEARCHTYPE(qry) );
416         fprintf( stream, "query name: '%s'\n", ADDRQUERY_NAME(qry) );
417         fprintf( stream, "search val: '%s'\n", ADDRQUERY_SEARCHVALUE(qry) );
418         fprintf( stream, "   queryID: %d\n",   ADDRQUERY_ID(qry) );
419         fprintf( stream, "   entries: %d\n",   qry->entriesRead );
420         fprintf( stream, "   elapsed: %d\n",   qry->elapsedTime );
421         fprintf( stream, " stop flag: %s\n",   qry->stopFlag  ? "yes" : "no" );
422         fprintf( stream, " busy flag: %s\n",   qry->busyFlag  ? "yes" : "no" );
423         fprintf( stream, " aged flag: %s\n",   qry->agedFlag  ? "yes" : "no" );
424         fprintf( stream, " completed: %s\n",   qry->completed ? "yes" : "no" );
425 }
426
427 /**
428  * Free linked lists of character strings.
429  * \param listName  List of common names.
430  * \param listAddr  List of addresses.
431  * \param listFirst List of first names.
432  * \param listLast  List of last names.
433  */
434 static void ldapqry_free_lists(
435                 GSList *listName, GSList *listAddr, GSList *listFirst,
436                 GSList *listLast )
437 {
438         mgu_free_list( listName );
439         mgu_free_list( listAddr );
440         mgu_free_list( listFirst );
441         mgu_free_list( listLast );
442 }
443
444 /**
445  * Add all LDAP attribute values to a list.
446  * \param ld LDAP handle.
447  * \param entry LDAP entry to process.
448  * \param attr  LDAP attribute.
449  * \return List of values.
450  */
451 static GSList *ldapqry_add_list_values(
452                 LDAP *ld, LDAPMessage *entry, char *attr )
453 {
454         GSList *list = NULL;
455         gint i;
456         gchar **vals;
457
458         if( ( vals = ldap_get_values( ld, entry, attr ) ) != NULL ) {
459                 for( i = 0; vals[i] != NULL; i++ ) {
460                         /* printf( "lv\t%s: %s\n", attr, vals[i] ); */
461                         list = g_slist_append( list, g_strdup( vals[i] ) );
462                 }
463         }
464         ldap_value_free( vals );
465         return list;
466 }
467
468 /**
469  * Add a single attribute value to a list.
470  * \param  ld    LDAP handle.
471  * \param  entry LDAP entry to process.
472  * \param  attr  LDAP attribute name to process.
473  * \return List of values; only one value will be present.
474  */
475 static GSList *ldapqry_add_single_value( LDAP *ld, LDAPMessage *entry, char *attr ) {
476         GSList *list = NULL;
477         gchar **vals;
478
479         if( ( vals = ldap_get_values( ld, entry, attr ) ) != NULL ) {
480                 if( vals[0] != NULL ) {
481                         /* printf( "sv\t%s: %s\n", attr, vals[0] ); */
482                         list = g_slist_append( list, g_strdup( vals[0] ) );
483                 }
484         }
485         ldap_value_free( vals );
486         return list;
487 }
488
489 /**
490  * Build an address list entry and append to list of address items. Name is formatted
491  * as "<first-name> <last-name>".
492  *
493  * \param  cache     Address cache to load.
494  * \param  qry Query object to process.
495  * \param  dn        DN for entry found on server.
496  * \param  listName  List of common names for entry; see notes below.
497  * \param  listAddr  List of EMail addresses for entry.
498  * \param  listFirst List of first names for entry.
499  * \param  listLast  List of last names for entry.
500  *
501  * \return List of ItemEMail objects.
502  *
503  * Notes:
504  * 1) Each LDAP server entry may have multiple LDAP attributes with the same
505  *    name. For example, a single entry for a person may have more than one
506  *    common name, email address, etc.
507 *
508  * 2) The DN for the entry is unique for the server.
509  */
510 static GList *ldapqry_build_items_fl(
511                 AddressCache *cache, LdapQuery *qry, gchar *dn,
512                 GSList *listName, GSList *listAddr, GSList *listFirst,
513                 GSList *listLast )
514 {
515         GSList *nodeAddress;
516         gchar *firstName = NULL, *lastName = NULL, *fullName = NULL;
517         gboolean allocated;
518         ItemPerson *person;
519         ItemEMail *email;
520         ItemFolder *folder;
521         GList *listReturn = NULL;
522
523         folder = ADDRQUERY_FOLDER(qry);
524         if( folder == NULL ) return listReturn;
525         if( listAddr == NULL ) return listReturn;
526
527         /* Find longest first name in list */
528         firstName = mgu_slist_longest_entry( listFirst );
529
530         /* Format last name */
531         if( listLast ) {
532                 lastName = listLast->data;
533         }
534
535         /* Find longest common name */
536         allocated = FALSE;
537         fullName = mgu_slist_longest_entry( listName );
538         if( fullName == NULL ) {
539                 /* Format a full name from first and last names */
540                 if( firstName ) {
541                         if( lastName ) {
542                                 fullName = g_strdup_printf( "%s %s", firstName, lastName );
543                         }
544                         else {
545                                 fullName = g_strdup_printf( "%s", firstName );
546                         }
547                 }
548                 else {
549                         if( lastName ) {
550                                 fullName = g_strdup_printf( "%s", lastName );
551                         }
552                 }
553                 if( fullName ) {
554                         g_strchug( fullName ); g_strchomp( fullName );
555                         allocated = TRUE;
556                 }
557         }
558
559         /* Add person into folder */            
560         person = addritem_create_item_person();
561         addritem_person_set_common_name( person, fullName );
562         addritem_person_set_first_name( person, firstName );
563         addritem_person_set_last_name( person, lastName );
564         addrcache_id_person( cache, person );
565         addritem_person_set_external_id( person, dn );
566         addrcache_folder_add_person( cache, ADDRQUERY_FOLDER(qry), person );
567
568         qry->entriesRead++;
569
570         /* Add each address item */
571         nodeAddress = listAddr;
572         while( nodeAddress ) {
573                 email = addritem_create_item_email();
574                 addritem_email_set_address( email, nodeAddress->data );
575                 addrcache_id_email( cache, email );
576                 addrcache_person_add_email( cache, person, email );
577                 addritem_person_add_email( person, email );
578                 listReturn = g_list_append( listReturn, email );
579                 nodeAddress = g_slist_next( nodeAddress );
580         }
581
582         /* Free any allocated memory */
583         if( allocated ) {
584                 g_free( fullName );
585         }
586         fullName = firstName = lastName = NULL;
587
588         return listReturn;
589 }
590
591 /**
592  * Process a single search entry.
593  * \param  cache Address cache to load.
594  * \param  qry   Query object to process.
595  * \param  ld    LDAP handle.
596  * \param  e     LDAP message.
597  * \return List of EMail objects found.
598  */
599 static GList *ldapqry_process_single_entry(
600                 AddressCache *cache, LdapQuery *qry, LDAP *ld, LDAPMessage *e )
601 {
602         char *dnEntry;
603         char *attribute;
604         LdapControl *ctl;
605         BerElement *ber;
606         GSList *listName = NULL, *listAddress = NULL;
607         GSList *listFirst = NULL, *listLast = NULL;
608         GList *listReturn;
609
610         listReturn = NULL;
611         ctl = qry->control;
612         dnEntry = ldap_get_dn( ld, e );
613         /* printf( "DN: %s\n", dnEntry ); */
614
615         /* Process all attributes */
616         for( attribute = ldap_first_attribute( ld, e, &ber ); attribute != NULL;
617                 attribute = ldap_next_attribute( ld, e, ber ) ) {
618
619                 if( strcasecmp( attribute, ctl->attribEMail ) == 0 ) {
620                         listAddress = ldapqry_add_list_values( ld, e, attribute );
621                 }
622                 else if( strcasecmp( attribute, ctl->attribCName ) == 0 ) {
623                         listName = ldapqry_add_list_values( ld, e, attribute );
624                 }
625                 else if( strcasecmp( attribute, ctl->attribFName ) == 0 ) {
626                         listFirst = ldapqry_add_list_values( ld, e, attribute );
627                 }
628                 else if( strcasecmp( attribute, ctl->attribLName ) == 0 ) {
629                         listLast = ldapqry_add_single_value( ld, e, attribute );
630                 }
631
632                 /* Free memory used to store attribute */
633                 ldap_memfree( attribute );
634         }
635
636         /* Format and add items to cache */
637         listReturn = ldapqry_build_items_fl(
638                 cache, qry, dnEntry, listName, listAddress, listFirst, listLast );
639
640         /* Free up */
641         ldapqry_free_lists( listName, listAddress, listFirst, listLast );
642         listName = listAddress = listFirst = listLast = NULL;
643
644         if( ber != NULL ) {
645                 ber_free( ber, 0 );
646         }
647         g_free( dnEntry );
648
649         return listReturn;
650 }
651
652 /**
653  * Check parameters that are required for a search. This should
654  * be called before performing a search.
655  * \param  qry Query object to process.
656  * \return <i>TRUE</i> if search criteria appear OK.
657  */
658 gboolean ldapqry_check_search( LdapQuery *qry ) {
659         LdapControl *ctl;
660         ADDRQUERY_RETVAL(qry) = LDAPRC_CRITERIA;
661
662         /* Test for control data */
663         ctl = qry->control;
664         if( ctl == NULL ) {
665                 return FALSE;
666         }
667
668         /* Test for search value */
669         if( ADDRQUERY_SEARCHVALUE(qry) == NULL ) {
670                 return FALSE;
671         }
672         if( strlen( ADDRQUERY_SEARCHVALUE(qry) ) < 1 ) {
673                 return FALSE;
674         }
675         ADDRQUERY_RETVAL(qry) = LDAPRC_SUCCESS;
676         return TRUE;
677 }
678
679 /**
680  * Touch the query. This nudges the touch time with the current time.
681  * \param qry Query object to process.
682  */
683 void ldapqry_touch( LdapQuery *qry ) {
684         qry->touchTime = time( NULL );
685         qry->agedFlag = FALSE;
686 }
687
688 /**
689  * Connect to LDAP server.
690  * \param  qry Query object to process.
691  * \return Error/status code.
692  */
693 static gint ldapqry_connect( LdapQuery *qry ) {
694         LdapControl *ctl;
695         LDAP *ld;
696         gint rc;
697         gint version;
698
699         /* Initialize connection */
700         /* printf( "===ldapqry_connect===\n" ); */
701         /* ldapqry_print( qry, stdout ); */
702         ctl = qry->control;
703         /* ldapctl_print( ctl, stdout ); */
704         /* printf( "======\n" ); */
705         ldapqry_touch( qry );
706         qry->startTime = qry->touchTime;
707         qry->elapsedTime = -1;
708         ADDRQUERY_RETVAL(qry) = LDAPRC_INIT;
709         if( ( ld = ldap_init( ctl->hostName, ctl->port ) ) == NULL ) {
710                 return ADDRQUERY_RETVAL(qry);
711         }
712         qry->ldap = ld;
713         ADDRQUERY_RETVAL(qry) = LDAPRC_STOP_FLAG;
714         if( ldapqry_get_stop_flag( qry ) ) {
715                 return ADDRQUERY_RETVAL(qry);
716         }
717         ldapqry_touch( qry );
718
719         /*
720         printf( "connected to LDAP host %s on port %d\n", ctl->hostName, ctl->port );
721         */
722
723 #ifdef USE_LDAP_TLS
724         /* Handle TLS */
725         version = LDAP_VERSION3;
726         rc = ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version );
727         if( rc == LDAP_OPT_SUCCESS ) {
728                 ctl->version = LDAP_VERSION3;
729         }
730
731         if( ctl->version == LDAP_VERSION3 ) {
732                 if( ctl->enableTLS ) {
733                         ADDRQUERY_RETVAL(qry) = LDAPRC_TLS;
734                         rc = ldap_start_tls_s( ld, NULL, NULL );
735                         /*
736                         printf( "rc=%d\n", rc );
737                         printf( "LDAP Status: set_option: %s\n", ldap_err2string( rc ) );
738                         */
739                         if( rc != LDAP_SUCCESS ) {
740                                 return ADDRQUERY_RETVAL(qry);
741                         }
742                 }
743         }
744 #endif
745
746         /* Bind to the server, if required */
747         ADDRQUERY_RETVAL(qry) = LDAPRC_BIND;
748         if( ctl->bindDN ) {
749                 if( * ctl->bindDN != '\0' ) {
750                         /* printf( "binding...\n" ); */
751                         rc = ldap_simple_bind_s( ld, ctl->bindDN, ctl->bindPass );
752                         /* printf( "rc=%d\n", rc ); */
753                         if( rc != LDAP_SUCCESS ) {
754                                 /*
755                                 printf( "LDAP Error: ldap_simple_bind_s: %s\n",
756                                         ldap_err2string( rc ) );
757                                 */
758                                 return ADDRQUERY_RETVAL(qry);
759                         }
760                 }
761         }
762         ADDRQUERY_RETVAL(qry) = LDAPRC_STOP_FLAG;
763         if( ldapqry_get_stop_flag( qry ) ) {
764                 return ADDRQUERY_RETVAL(qry);
765         }
766         ldapqry_touch( qry );
767
768         ADDRQUERY_RETVAL(qry) = LDAP_SUCCESS;
769
770         return ADDRQUERY_RETVAL(qry);
771 }
772
773 /**
774  * Connect to LDAP server.
775  * \param  qry Query object to process.
776  * \return Error/status code.
777  */
778 static gint ldapqry_disconnect( LdapQuery *qry ) {
779         /* Disconnect */
780         if( qry->ldap ) ldap_unbind( qry->ldap );
781         qry->ldap = NULL;
782
783         ldapqry_touch( qry );
784         qry->elapsedTime = qry->touchTime - qry->startTime;
785
786         return ADDRQUERY_RETVAL(qry);
787 }
788
789 /**
790  * Perform the LDAP search, reading LDAP entries into cache.
791  * Note that one LDAP entry can have multiple values for many of its
792  * attributes. If these attributes are E-Mail addresses; these are
793  * broken out into separate address items. For any other attribute,
794  * only the first occurrence is read.
795  * 
796  * \param  qry Query object to process.
797  * \return Error/status code.
798  */
799 static gint ldapqry_search_retrieve( LdapQuery *qry ) {
800         LdapControl *ctl;
801         LDAP *ld;
802         LDAPMessage *result, *e;
803         char **attribs;
804         gchar *criteria;
805         gboolean searchFlag;
806         gboolean entriesFound;
807         gboolean first;
808         struct timeval timeout;
809         gint rc;
810         AddressCache *cache;
811         GList *listEMail;
812
813         /* Initialize some variables */
814         ld = qry->ldap;
815         ctl = qry->control;
816         cache = qry->server->addressCache;
817         timeout.tv_sec = ctl->timeOut;
818         timeout.tv_usec = 0L;
819         entriesFound = FALSE;
820         ADDRQUERY_RETVAL(qry) = LDAPRC_SUCCESS;
821
822         /* Define all attributes we are interested in. */
823         attribs = ldapctl_attribute_array( ctl );
824
825         /* Create LDAP search string */
826         criteria = ldapctl_format_criteria( ctl, ADDRQUERY_SEARCHVALUE(qry) );
827         /* printf( "Search criteria ::%s::\n", criteria ); */
828
829         /*
830          * Execute the search - this step may take some time to complete
831          * depending on network traffic and server response time.
832          */
833         ADDRQUERY_RETVAL(qry) = LDAPRC_TIMEOUT;
834         rc = ldap_search_ext_s( ld, ctl->baseDN, LDAP_SCOPE_SUBTREE, criteria,
835                 attribs, 0, NULL, NULL, &timeout, 0, &result );
836         ldapctl_free_attribute_array( attribs );
837         g_free( criteria );
838         criteria = NULL;
839         if( rc == LDAP_TIMEOUT ) {
840                 return ADDRQUERY_RETVAL(qry);
841         }
842         ADDRQUERY_RETVAL(qry) = LDAPRC_SEARCH;
843
844         /* Test valid returns */
845         searchFlag = FALSE;
846         if( rc == LDAP_ADMINLIMIT_EXCEEDED ) {
847                 searchFlag = TRUE;
848         }
849         else if( rc == LDAP_SUCCESS ) {
850                 searchFlag = TRUE;
851         }
852         else if( rc == LDAP_PARTIAL_RESULTS ) {
853                 searchFlag = TRUE;
854         }
855         else {
856                 /*
857                 printf( "LDAP Error: ldap_search_st: %d\n", rc );
858                 printf( "LDAP Error: ldap_search_st: %s\n", ldap_err2string( rc ) );
859                 */
860                 return ADDRQUERY_RETVAL(qry);
861         }
862         ADDRQUERY_RETVAL(qry) = LDAPRC_STOP_FLAG;
863
864         /*
865         printf( "Total results are: %d\n", ldap_count_entries( ld, result ) );
866         */
867
868         /* Process results */
869         first = TRUE;
870         while( searchFlag ) {
871                 ldapqry_touch( qry );
872                 if( qry->entriesRead >= ctl->maxEntries ) break;                
873
874                 /* Test for stop */             
875                 if( ldapqry_get_stop_flag( qry ) ) {
876                         break;
877                 }
878
879                 /* Retrieve entry */            
880                 if( first ) {
881                         first = FALSE;
882                         e = ldap_first_entry( ld, result );
883                 }
884                 else {
885                         e = ldap_next_entry( ld, e );
886                 }
887                 if( e == NULL ) break;
888                 entriesFound = TRUE;
889
890                 /* Setup a critical section here */
891                 pthread_mutex_lock( qry->mutexEntry );
892
893                 /* Process entry */
894                 listEMail = ldapqry_process_single_entry( cache, qry, ld, e );
895
896                 /* Process callback */
897                 if( qry->callBackEntry ) {
898                         qry->callBackEntry( qry, ADDRQUERY_ID(qry), listEMail, qry->data );
899                 }
900                 else {
901                         g_list_free( listEMail );
902                 }
903                 pthread_mutex_unlock( qry->mutexEntry );
904         }
905
906         /* Free up and disconnect */
907         ldap_msgfree( result );
908
909         if( searchFlag ) {
910                 if( entriesFound ) {
911                         ADDRQUERY_RETVAL(qry) = LDAPRC_SUCCESS;
912                 }
913                 else {
914                         ADDRQUERY_RETVAL(qry) = LDAPRC_NOENTRIES;
915                 }
916         }
917
918         return ADDRQUERY_RETVAL(qry);
919 }
920
921 /**
922  * Connection, perform search and disconnect.
923  * \param  qry Query object to process.
924  * \return Error/status code.
925  */
926 static gint ldapqry_perform_search( LdapQuery *qry ) {
927         /* Check search criteria */     
928         if( ! ldapqry_check_search( qry ) ) {
929                 return ADDRQUERY_RETVAL(qry);
930         }
931
932         /* Connect */
933         qry->ldap = NULL;
934         ldapqry_connect( qry );
935         if( ADDRQUERY_RETVAL(qry) == LDAPRC_SUCCESS ) {
936                 /* Perform search */
937                 ldapqry_search_retrieve( qry );
938         }
939         /* Disconnect */
940         ldapqry_disconnect( qry );
941         qry->ldap = NULL;
942
943         return ADDRQUERY_RETVAL(qry);
944 }
945
946 /**
947  * Wrapper around search.
948  * \param  qry Query object to process.
949  * \return Error/status code.
950  */
951 gint ldapqry_search( LdapQuery *qry ) {
952         gint retVal;
953
954         g_return_val_if_fail( qry != NULL, -1 );
955         g_return_val_if_fail( qry->control != NULL, -1 );
956
957         ldapqry_touch( qry );
958         qry->completed = FALSE;
959
960         /* Setup pointer to thread specific area */
961         pthread_setspecific( _queryThreadKey_, qry );
962
963         pthread_detach( pthread_self() );
964         
965         /* Now perform the search */
966         qry->entriesRead = 0;
967         ADDRQUERY_RETVAL(qry) = LDAPRC_SUCCESS;
968         ldapqry_set_busy_flag( qry, TRUE );
969         ldapqry_set_stop_flag( qry, FALSE );
970         if( ADDRQUERY_SEARCHTYPE(qry) == ADDRSEARCH_LOCATE ) {
971                 retVal = ldapqry_perform_locate( qry );
972         }
973         else {
974                 retVal = ldapqry_perform_search( qry );
975         }
976         if( retVal == LDAPRC_SUCCESS ) {
977                 qry->server->addressCache->dataRead = TRUE;
978                 qry->server->addressCache->accessFlag = FALSE;
979                 if( ldapqry_get_stop_flag( qry ) ) {
980                         /*
981                         printf( "Search was terminated prematurely\n" );
982                         */
983                 }
984                 else {
985                         ldapqry_touch( qry );
986                         qry->completed = TRUE;
987                         /*
988                         printf( "Search ran to completion\n" );
989                         */
990                 }
991         }
992         ldapqry_set_stop_flag( qry, TRUE );
993         ldapqry_set_busy_flag( qry, FALSE );
994
995         /* Process callback */  
996         if( qry->callBackEnd ) {
997                 qry->callBackEnd( qry, ADDRQUERY_ID(qry), ADDRQUERY_RETVAL(qry), qry->data );
998         }
999
1000         return ADDRQUERY_RETVAL(qry);
1001 }
1002
1003 /**
1004  * Read data into list using a background thread. Callback function will be
1005  * notified when search is complete.
1006  * \param  qry Query object to process.
1007  * \return Error/status code.
1008  */
1009 gint ldapqry_read_data_th( LdapQuery *qry ) {
1010         g_return_val_if_fail( qry != NULL, -1 );
1011         g_return_val_if_fail( qry->control != NULL, -1 );
1012
1013         ldapqry_set_stop_flag( qry, FALSE );
1014         ldapqry_touch( qry );
1015         if( ldapqry_check_search( qry ) ) {
1016                 if( ADDRQUERY_RETVAL(qry) == LDAPRC_SUCCESS ) {
1017                         /*
1018                         printf( "Starting LDAP search thread\n");
1019                         */
1020                         ldapqry_set_busy_flag( qry, TRUE );
1021                         qry->thread = g_malloc0( sizeof( pthread_t ) );
1022
1023                         /* Setup thread */                      
1024                         pthread_create( qry->thread, NULL,
1025                                 (void *) ldapqry_search, (void *) qry );
1026                 }
1027         }
1028         return ADDRQUERY_RETVAL(qry);
1029 }
1030
1031 /**
1032  * Cleanup LDAP thread data. This function will be called when each thread
1033  * exits. Note that the thread object will be freed by the kernel.
1034  * \param ptr Pointer to object being destroyed (a query object in this case).
1035  */
1036 static void ldapqry_destroyer( void * ptr ) {
1037         LdapQuery *qry;
1038
1039         qry = ( LdapQuery * ) ptr;
1040         /*
1041         printf( "ldapqry_destroyer::%d::%s\n", (int) pthread_self(), ADDRQUERY_NAME(qry) );
1042         */
1043
1044         /* Perform any destruction here */
1045         if( qry->control != NULL ) {
1046                 ldapctl_free( qry->control );
1047         }
1048         qry->control = NULL;
1049         qry->thread = NULL;
1050         ldapqry_set_busy_flag( qry, FALSE );
1051 }
1052
1053 /**
1054  * Cancel thread associated with query.
1055  * \param qry Query object to process.
1056  */
1057 void ldapqry_cancel( LdapQuery *qry ) {
1058         g_return_if_fail( qry != NULL );
1059
1060         /*
1061         printf( "cancelling::%d::%s\n", (int) pthread_self(), ADDRQUERY_NAME(qry) );
1062         */
1063         if( ldapqry_get_busy_flag( qry ) ) {
1064                 if( qry->thread ) {
1065                         /* printf( "calling pthread_cancel\n" ); */
1066                         pthread_cancel( * qry->thread );
1067                 }
1068         }
1069 }
1070
1071 /**
1072  * Initialize LDAP query. This function should be called once before executing
1073  * any LDAP queries to initialize thread specific data.
1074  */
1075 void ldapqry_initialize( void ) {
1076         /* printf( "ldapqry_initialize...\n" ); */
1077         if( ! _queryThreadInit_ ) {
1078                 /*
1079                 printf( "ldapqry_initialize::creating thread specific area\n" );
1080                 */
1081                 pthread_key_create( &_queryThreadKey_, ldapqry_destroyer );
1082                 _queryThreadInit_ = TRUE;
1083         }
1084         /* printf( "ldapqry_initialize... done!\n" ); */
1085 }
1086
1087 /**
1088  * Age the query based on LDAP control parameters.
1089  * \param qry    Query object to process.
1090  * \param maxAge Maximum age of query (in seconds).
1091  */
1092 void ldapqry_age( LdapQuery *qry, gint maxAge ) {
1093         gint age;
1094
1095         g_return_if_fail( qry != NULL );
1096
1097         /* Limit the time that queries can hang around */       
1098         if( maxAge < 1 ) maxAge = LDAPCTL_MAX_QUERY_AGE;
1099
1100         /* Check age of query */
1101         age = time( NULL ) - qry->touchTime;
1102         if( age > maxAge ) {
1103                 qry->agedFlag = TRUE;
1104         }
1105 }
1106
1107 /**
1108  * Delete folder associated with query results.
1109  * \param qry Query object to process.
1110  */
1111 void ldapqry_delete_folder( LdapQuery *qry ) {
1112         AddressCache *cache;
1113         ItemFolder *folder;
1114
1115         g_return_if_fail( qry != NULL );
1116
1117         folder = ADDRQUERY_FOLDER(qry);
1118         if( folder ) {
1119                 cache = qry->server->addressCache;
1120                 folder = addrcache_remove_folder_delete( cache, folder );
1121                 if( folder ) {
1122                         addritem_free_item_folder( folder );
1123                 }
1124                 ADDRQUERY_FOLDER(qry) = NULL;
1125         }
1126 }
1127
1128 /**
1129  * Create a name/value pair object.
1130  * \param n Name.
1131  * \param v Value.
1132  * \return Initialized object.
1133  */
1134 static NameValuePair *ldapqry_create_name_value( const gchar *n, const gchar *v ) {
1135         NameValuePair *nvp = g_new0( NameValuePair, 1 );
1136
1137         nvp->name = g_strdup( n );
1138         nvp->value = g_strdup( v );
1139         return nvp;
1140 }
1141
1142 /**
1143  * Free up name/value pair object.
1144  * \param nvp Name/value object.
1145  */
1146 void ldapqry_free_name_value( NameValuePair *nvp ) {
1147         if( nvp ) {
1148                 g_free( nvp->name );
1149                 g_free( nvp->value );
1150                 nvp->name = nvp->value = NULL;
1151                 g_free( nvp );
1152         }
1153 }
1154
1155 /**
1156  * Print name/value pair object for debug.
1157  * \param nvp    Name/value object.
1158  * \param stream Output stream.
1159  */
1160 void ldapqry_print_name_value( NameValuePair *nvp, FILE *stream ) {
1161         if( nvp ) {
1162                 fprintf( stream, "n/v ::%s::%s::\n", nvp->name, nvp->value );
1163         }
1164 }
1165
1166 /**
1167  * Free up a list name/value pair objects.
1168  * \param list List of name/value objects.
1169  */
1170 void ldapqry_free_list_name_value( GList *list ) {
1171         GList *node;
1172
1173         node = list;
1174         while( node ) {
1175                 NameValuePair *nvp = ( NameValuePair * ) node->data;
1176                 ldapqry_free_name_value( nvp );
1177                 node->data = NULL;
1178                 node = g_list_next( node );
1179         }
1180         g_list_free( list );
1181 }
1182
1183 /**
1184  * Load a list of name/value pairs from LDAP attributes.
1185  * \param  ld          LDAP handle.
1186  * \param  e          LDAP message.
1187  * \param  attr       Attribute name.
1188  * \param  listValues List to populate.
1189  * \return List of attribute name/value pairs.
1190  */
1191 static GList *ldapqry_load_attrib_values(
1192                 LDAP *ld, LDAPMessage *entry, char *attr,
1193                 GList *listValues )
1194 {
1195         GList *list = NULL;
1196         gint i;
1197         gchar **vals;
1198         NameValuePair *nvp;
1199
1200         list = listValues;
1201         if( ( vals = ldap_get_values( ld, entry, attr ) ) != NULL ) {
1202                 for( i = 0; vals[i] != NULL; i++ ) {
1203                         nvp = ldapqry_create_name_value( attr, vals[i] );
1204                         list = g_list_append( list, nvp );
1205                 }
1206         }
1207         ldap_value_free( vals );
1208         return list;
1209 }
1210
1211 /**
1212  * Fetch a list of all attributes.
1213  * \param  ld    LDAP handle.
1214  * \param  e     LDAP message.
1215  * \return List of attribute name/value pairs.
1216  */
1217 static GList *ldapqry_fetch_attribs( LDAP *ld, LDAPMessage *e )
1218 {
1219         char *attribute;
1220         BerElement *ber;
1221         GList *listValues = NULL;
1222
1223         /* Process all attributes */
1224         for( attribute = ldap_first_attribute( ld, e, &ber ); attribute != NULL;
1225                 attribute = ldap_next_attribute( ld, e, ber ) ) {
1226                 listValues = ldapqry_load_attrib_values( ld, e, attribute, listValues );
1227                 ldap_memfree( attribute );
1228         }
1229
1230         /* Free up */
1231         if( ber != NULL ) {
1232                 ber_free( ber, 0 );
1233         }
1234         return listValues;
1235 }
1236
1237 #define CRITERIA_SINGLE "(objectclass=*)"
1238
1239 /**
1240  * Perform the data retrieval for a specific LDAP record.
1241  * 
1242  * \param  qry Query object to process.
1243  * \return Error/status code.
1244  */
1245 static gint ldapqry_locate_retrieve( LdapQuery *qry ) {
1246         LdapControl *ctl;
1247         LDAP *ld;
1248         LDAPMessage *result, *e;
1249         gboolean entriesFound;
1250         gboolean first;
1251         struct timeval timeout;
1252         gint rc;
1253         gchar *dn;
1254         GList *listValues;
1255
1256         /* Initialize some variables */
1257         ld = qry->ldap;
1258         ctl = qry->control;
1259         dn = ADDRQUERY_SEARCHVALUE(qry);
1260         timeout.tv_sec = ctl->timeOut;
1261         timeout.tv_usec = 0L;
1262         entriesFound = FALSE;
1263
1264         /*
1265          * Execute the search - this step may take some time to complete
1266          * depending on network traffic and server response time.
1267          */
1268         ADDRQUERY_RETVAL(qry) = LDAPRC_TIMEOUT;
1269         rc = ldap_search_ext_s( ld, dn, LDAP_SCOPE_BASE, CRITERIA_SINGLE,
1270                 NULL, 0, NULL, NULL, &timeout, 0, &result );
1271         if( rc == LDAP_TIMEOUT ) {
1272                 return ADDRQUERY_RETVAL(qry);
1273         }
1274         ADDRQUERY_RETVAL(qry) = LDAPRC_SEARCH;
1275         if( rc != LDAP_SUCCESS ) {
1276                 /*
1277                 printf( "LDAP Error: ldap_search_st: %s\n", ldap_err2string( rc ) );
1278                 */
1279                 return ADDRQUERY_RETVAL(qry);
1280         }
1281
1282         /*
1283         printf( "Total results are: %d\n", ldap_count_entries( ld, result ) );
1284         */
1285
1286         /* Process results */
1287         ADDRQUERY_RETVAL(qry) = LDAPRC_STOP_FLAG;
1288         first = TRUE;
1289         while( TRUE ) {
1290                 ldapqry_touch( qry );
1291                 if( qry->entriesRead >= ctl->maxEntries ) break;                
1292
1293                 /* Test for stop */
1294                 if( ldapqry_get_stop_flag( qry ) ) {
1295                         break;
1296                 }
1297
1298                 /* Retrieve entry */            
1299                 if( first ) {
1300                         first = FALSE;
1301                         e = ldap_first_entry( ld, result );
1302                 }
1303                 else {
1304                         e = ldap_next_entry( ld, e );
1305                 }
1306                 if( e == NULL ) break;
1307
1308                 entriesFound = TRUE;
1309
1310                 /* Setup a critical section here */
1311                 pthread_mutex_lock( qry->mutexEntry );
1312
1313                 /* Process entry */
1314                 listValues = ldapqry_fetch_attribs( ld, e );
1315
1316                 /* Process callback */
1317                 if( qry->callBackEntry ) {
1318                         qry->callBackEntry( qry, ADDRQUERY_ID(qry), listValues, qry->data );
1319                 }
1320                 ldapqry_free_list_name_value( listValues );
1321                 listValues = NULL;
1322
1323                 pthread_mutex_unlock( qry->mutexEntry );
1324         }
1325
1326         /* Free up and disconnect */
1327         ldap_msgfree( result );
1328
1329         if( entriesFound ) {
1330                 ADDRQUERY_RETVAL(qry) = LDAPRC_SUCCESS;
1331         }
1332         else {
1333                 ADDRQUERY_RETVAL(qry) = LDAPRC_NOENTRIES;
1334         }
1335
1336         return ADDRQUERY_RETVAL(qry);
1337 }
1338
1339 /**
1340  * Perform the search to locate a specific LDAP record identified by
1341  * distinguished name (dn).
1342  * 
1343  * \param  qry Query object to process.
1344  * \return Error/status code.
1345  */
1346 gint ldapqry_perform_locate( LdapQuery *qry ) {
1347         /* Connect */
1348         qry->ldap = NULL;
1349         ldapqry_connect( qry );
1350         if( ADDRQUERY_RETVAL(qry) == LDAPRC_SUCCESS ) {
1351                 /* Perform search */
1352                 ldapqry_locate_retrieve( qry );
1353         }
1354         /* Disconnect */
1355         ldapqry_disconnect( qry );
1356         qry->ldap = NULL;
1357
1358         /* Process callback */  
1359         if( qry->callBackEnd ) {
1360                 qry->callBackEnd( qry, ADDRQUERY_ID(qry), ADDRQUERY_RETVAL(qry), qry->data );
1361         }
1362
1363         return ADDRQUERY_RETVAL(qry);
1364 }
1365
1366 /**
1367  * Remove results (folder and data) for specified LDAP query.
1368  * \param  qry Query object to process.
1369  * \return TRUE if folder deleted successfully.
1370  */
1371 gboolean ldapquery_remove_results( LdapQuery *qry ) {
1372         gboolean retVal = FALSE;
1373
1374         /* Set query as aged - will be retired on a later call */
1375         ldapqry_set_aged_flag( qry, TRUE );
1376         /*
1377         printf( "ldapquery_remove_results...\n" );
1378         printf( "testing busy flag...\n" );
1379         */
1380         if( ldapqry_get_busy_flag( qry ) ) {
1381                 /* Query is still busy - cancel query */
1382                 /* printf( "\tquery is still busy running...\n" ); */
1383                 ldapqry_set_stop_flag( qry, TRUE );
1384
1385                 /* ldapqry_cancel( qry ); */
1386         }
1387         else {
1388                 /* Delete folder */
1389                 /* printf( "\tquery can be deleted!\n" ); */
1390                 /* ldapqry_delete_folder( qry ); */
1391                 retVal = TRUE;
1392                 /* printf( "\tquery deleted!\n" ); */
1393         }
1394         return retVal;
1395 }
1396
1397 #endif  /* USE_LDAP */
1398
1399 /*
1400  * End of Source.
1401  */
1402
1403