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