2007-10-08 [colin] 3.0.2cvs38
[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         ADDRQUERY_NAME(qry) = mgu_replace_string( ADDRQUERY_NAME(qry), value );
119         if (ADDRQUERY_NAME(qry) == NULL)
120                 return;
121         g_strstrip( ADDRQUERY_NAME(qry) );
122         debug_print("set name: %s\n", ADDRQUERY_NAME(qry));
123 }
124
125 /**
126  * Specify search value to be used.
127  * \param qry Query object.
128  * \param value 
129  */
130 void ldapqry_set_search_value( LdapQuery *qry, const gchar *value ) {
131         g_return_if_fail( qry != NULL );
132         ADDRQUERY_SEARCHVALUE(qry) = mgu_replace_string( ADDRQUERY_SEARCHVALUE(qry), value );
133         if (ADDRQUERY_SEARCHVALUE(qry) == NULL)
134                 return;
135         g_strstrip( ADDRQUERY_SEARCHVALUE(qry) );
136         debug_print("search value: %s\n", ADDRQUERY_SEARCHVALUE(qry));
137 }
138
139 /**
140  * Specify query type.
141  * \param qry Query object.
142  * \param value Query type, either:
143  * <ul>
144  * <li><code>LDAPQUERY_NONE</code></li>
145  * <li><code>LDAPQUERY_STATIC</code></li>
146  * <li><code>LDAPQUERY_DYNAMIC</code></li>
147  * </ul>
148  */
149 /*
150 void ldapqry_set_query_type( LdapQuery* qry, const gint value ) {
151         ADDRQUERY_TYPE(qry) = value;
152 }
153 */
154
155 /**
156  * Specify search type.
157  * \param qry   Query object.
158  * \param value Type.
159  */
160 void ldapqry_set_search_type( LdapQuery *qry, const AddrSearchType value ) {
161         g_return_if_fail( qry != NULL );
162         ADDRQUERY_SEARCHTYPE(qry) = value;
163 }
164
165 /**
166  * Specify query ID.
167  * \param qry Query object.
168  * \param value ID for the query.
169  */
170 void ldapqry_set_query_id( LdapQuery* qry, const gint value ) {
171         g_return_if_fail( 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         ldapsrv_set_options (ctl->timeOut, NULL);
682
683         uri = g_strdup_printf("ldap%s://%s:%d",
684                                 ctl->enableSSL?"s":"",
685                                 ctl->hostName, ctl->port);
686         ldap_initialize(&ld, uri);
687         g_free(uri);
688
689         if (ld == NULL)
690                 return ADDRQUERY_RETVAL(qry);
691
692         qry->ldap = ld;
693         ADDRQUERY_RETVAL(qry) = LDAPRC_STOP_FLAG;
694         if( ldapqry_get_stop_flag( qry ) ) {
695                 return ADDRQUERY_RETVAL(qry);
696         }
697         ldapqry_touch( qry );
698
699         debug_print("connected to LDAP host %s on port %d\n",
700                         ctl->hostName?ctl->hostName:"null", ctl->port);
701
702 #ifdef USE_LDAP_TLS
703         /* Handle TLS */
704         version = LDAP_VERSION3;
705         rc = ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version );
706         if( rc == LDAP_OPT_SUCCESS ) {
707                 ctl->version = LDAP_VERSION3;
708         }
709
710         if( ctl->version == LDAP_VERSION3 ) {
711                 if( ctl->enableTLS && !ctl->enableSSL ) {
712                         ADDRQUERY_RETVAL(qry) = LDAPRC_TLS;
713                         rc = ldap_start_tls_s( ld, NULL, NULL );
714                         
715                         debug_print("rc=%d\n", rc);
716                         debug_print("LDAP Status: set_option: %s\n", ldap_err2string(rc));
717
718                         if( rc != LDAP_SUCCESS ) {
719                                 return ADDRQUERY_RETVAL(qry);
720                         }
721                 }
722         }
723 #endif
724
725         /* Bind to the server, if required */
726         ADDRQUERY_RETVAL(qry) = LDAPRC_BIND;
727         if( ctl->bindDN ) {
728                 if( * ctl->bindDN != '\0' ) {
729                         debug_print("binding...\n");
730                         rc = claws_ldap_simple_bind_s( ld, ctl->bindDN, ctl->bindPass );
731                         debug_print("rc=%d\n", rc);
732                         if( rc != LDAP_SUCCESS ) {
733                                 debug_print("LDAP Error: ldap_simple_bind_s: %s\n",     ldap_err2string(rc));
734                                 return ADDRQUERY_RETVAL(qry);
735                         }
736                 }
737         }
738         ADDRQUERY_RETVAL(qry) = LDAPRC_STOP_FLAG;
739         if( ldapqry_get_stop_flag( qry ) ) {
740                 return ADDRQUERY_RETVAL(qry);
741         }
742         ldapqry_touch( qry );
743
744         ADDRQUERY_RETVAL(qry) = LDAP_SUCCESS;
745
746         return ADDRQUERY_RETVAL(qry);
747 }
748
749 /**
750  * Connect to LDAP server.
751  * \param  qry Query object to process.
752  * \return Error/status code.
753  */
754 static gint ldapqry_disconnect( LdapQuery *qry ) {
755         /* Disconnect */
756         if( qry->ldap ) ldap_unbind_ext( qry->ldap, NULL, NULL );
757         qry->ldap = NULL;
758
759         ldapqry_touch( qry );
760         qry->elapsedTime = qry->touchTime - qry->startTime;
761
762         return ADDRQUERY_RETVAL(qry);
763 }
764
765 /**
766  * Perform the LDAP search, reading LDAP entries into cache.
767  * Note that one LDAP entry can have multiple values for many of its
768  * attributes. If these attributes are E-Mail addresses; these are
769  * broken out into separate address items. For any other attribute,
770  * only the first occurrence is read.
771  * 
772  * \param  qry Query object to process.
773  * \return Error/status code.
774  */
775 static gint ldapqry_search_retrieve( LdapQuery *qry ) {
776         LdapControl *ctl;
777         LDAP *ld;
778         LDAPMessage *result, *e = NULL;
779         char **attribs;
780         gchar *criteria;
781         gboolean searchFlag;
782         gboolean entriesFound;
783         gboolean first;
784         struct timeval timeout;
785         gint rc;
786         AddressCache *cache;
787         GList *listEMail;
788
789         /* Initialize some variables */
790         ld = qry->ldap;
791         ctl = qry->control;
792         cache = qry->server->addressCache;
793         timeout.tv_sec = ctl->timeOut;
794         timeout.tv_usec = 0L;
795         entriesFound = FALSE;
796         ADDRQUERY_RETVAL(qry) = LDAPRC_SUCCESS;
797
798         /* Define all attributes we are interested in. */
799         attribs = ldapctl_full_attribute_array( ctl );
800
801         /* Create LDAP search string */
802         criteria = ldapctl_format_criteria( ctl, ADDRQUERY_SEARCHVALUE(qry) );
803         debug_print("Search criteria ::%s::\n", criteria?criteria:"null");
804
805         /*
806          * Execute the search - this step may take some time to complete
807          * depending on network traffic and server response time.
808          */
809         ADDRQUERY_RETVAL(qry) = LDAPRC_TIMEOUT;
810         rc = ldap_search_ext_s( ld, ctl->baseDN, LDAP_SCOPE_SUBTREE, criteria,
811                 attribs, 0, NULL, NULL, &timeout, 0, &result );
812         debug_print("LDAP Error: ldap_search_st: %d\n", rc);
813         debug_print("LDAP Error: ldap_search_st: %s\n", ldap_err2string(rc));
814         ldapctl_free_attribute_array( attribs );
815         g_free( criteria );
816         criteria = NULL;
817         if( rc == LDAP_TIMEOUT ) {
818                 return ADDRQUERY_RETVAL(qry);
819         }
820         ADDRQUERY_RETVAL(qry) = LDAPRC_SEARCH;
821
822         /* Test valid returns */
823         searchFlag = FALSE;
824         if( rc == LDAP_ADMINLIMIT_EXCEEDED ) {
825                 searchFlag = TRUE;
826         }
827         else if( rc == LDAP_SUCCESS ) {
828                 searchFlag = TRUE;
829         }
830         else if( rc == LDAP_PARTIAL_RESULTS ) {
831                 searchFlag = TRUE;
832         }
833         else {
834                 debug_print("LDAP Error: ldap_search_st: %d\n", rc);
835                 debug_print("LDAP Error: ldap_search_st: %s\n", ldap_err2string(rc));
836                 return ADDRQUERY_RETVAL(qry);
837         }
838         ADDRQUERY_RETVAL(qry) = LDAPRC_STOP_FLAG;
839
840         debug_print("Total results are: %d\n", ldap_count_entries(ld, result));
841
842         /* Process results */
843         first = TRUE;
844         while( searchFlag ) {
845                 ldapqry_touch( qry );
846                 if( qry->entriesRead >= ctl->maxEntries ) break;                
847
848                 /* Test for stop */             
849                 if( ldapqry_get_stop_flag( qry ) ) {
850                         break;
851                 }
852
853                 /* Retrieve entry */            
854                 if( first ) {
855                         first = FALSE;
856                         e = ldap_first_entry( ld, result );
857                 }
858                 else {
859                         e = ldap_next_entry( ld, e );
860                 }
861                 if( e == NULL ) break;
862                 entriesFound = TRUE;
863
864                 /* Setup a critical section here */
865                 pthread_mutex_lock( qry->mutexEntry );
866
867                 /* Process entry */
868                 listEMail = ldapqry_process_single_entry( cache, qry, ld, e );
869
870                 /* Process callback */
871                 if( qry->callBackEntry ) {
872                         qry->callBackEntry( qry, ADDRQUERY_ID(qry), listEMail, qry->data );
873                 }
874                 else {
875                         /*if (debug_get_mode()) {
876                                 GList *node = listEMail;
877                                 while (node) {
878                                         addritem_print_item_email(node->data, stdout);
879                                         node = g_list_next(node);
880                                 }
881                         }*/
882                         g_list_free( listEMail );
883                 }
884                 pthread_mutex_unlock( qry->mutexEntry );
885         }
886
887         /* Free up and disconnect */
888         ldap_msgfree( result );
889
890         if( searchFlag ) {
891                 if( entriesFound ) {
892                         ADDRQUERY_RETVAL(qry) = LDAPRC_SUCCESS;
893                 }
894                 else {
895                         ADDRQUERY_RETVAL(qry) = LDAPRC_NOENTRIES;
896                 }
897         }
898
899         return ADDRQUERY_RETVAL(qry);
900 }
901
902 /**
903  * Connection, perform search and disconnect.
904  * \param  qry Query object to process.
905  * \return Error/status code.
906  */
907 static gint ldapqry_perform_search( LdapQuery *qry ) {
908         /* Check search criteria */     
909         if( ! ldapqry_check_search( qry ) ) {
910                 return ADDRQUERY_RETVAL(qry);
911         }
912
913         /* Connect */
914         qry->ldap = NULL;
915         ldapqry_connect( qry );
916         if( ADDRQUERY_RETVAL(qry) == LDAPRC_SUCCESS ) {
917                 /* Perform search */
918                 ldapqry_search_retrieve( qry );
919         }
920         /* Disconnect */
921         ldapqry_disconnect( qry );
922         qry->ldap = NULL;
923
924         return ADDRQUERY_RETVAL(qry);
925 }
926
927 static gint ldapqry_perform_locate( LdapQuery *qry );
928
929 /**
930  * Wrapper around search.
931  * \param  qry Query object to process.
932  * \return Error/status code.
933  */
934 static gint ldapqry_search( LdapQuery *qry ) {
935         gint retVal;
936
937         g_return_val_if_fail( qry != NULL, -1 );
938         g_return_val_if_fail( qry->control != NULL, -1 );
939
940         ldapqry_touch( qry );
941         qry->completed = FALSE;
942
943         /* Setup pointer to thread specific area */
944         pthread_setspecific( _queryThreadKey_, qry );
945
946         pthread_detach( pthread_self() );
947         
948         /* Now perform the search */
949         qry->entriesRead = 0;
950         ADDRQUERY_RETVAL(qry) = LDAPRC_SUCCESS;
951         ldapqry_set_busy_flag( qry, TRUE );
952         ldapqry_set_stop_flag( qry, FALSE );
953         if( ADDRQUERY_SEARCHTYPE(qry) == ADDRSEARCH_LOCATE ) {
954                 retVal = ldapqry_perform_locate( qry );
955         }
956         else {
957                 retVal = ldapqry_perform_search( qry );
958         }
959         if( retVal == LDAPRC_SUCCESS ) {
960                 qry->server->addressCache->dataRead = TRUE;
961                 qry->server->addressCache->accessFlag = FALSE;
962                 if( ldapqry_get_stop_flag( qry ) ) {
963                         debug_print("Search was terminated prematurely\n");
964                 }
965                 else {
966                         ldapqry_touch( qry );
967                         qry->completed = TRUE;
968                         debug_print("Search ran to completion\n");
969                 }
970         }
971         ldapqry_set_stop_flag( qry, TRUE );
972         ldapqry_set_busy_flag( qry, FALSE );
973
974         /* Process callback */  
975         if( qry->callBackEnd ) {
976                 g_timeout_add(0, callbackend, qry);
977         }
978
979         return ADDRQUERY_RETVAL(qry);
980 }
981
982 /**
983  * Read data into list using a background thread. Callback function will be
984  * notified when search is complete.
985  * \param  qry Query object to process.
986  * \return Error/status code.
987  */
988 gint ldapqry_read_data_th( LdapQuery *qry ) {
989         g_return_val_if_fail( qry != NULL, -1 );
990         g_return_val_if_fail( qry->control != NULL, -1 );
991
992         ldapqry_set_stop_flag( qry, FALSE );
993         ldapqry_touch( qry );
994         if( ldapqry_check_search( qry ) ) {
995                 if( ADDRQUERY_RETVAL(qry) == LDAPRC_SUCCESS ) {
996                         debug_print("Starting LDAP search thread\n");
997                         ldapqry_set_busy_flag( qry, TRUE );
998                         qry->thread = g_malloc0( sizeof( pthread_t ) );
999
1000                         /* Setup thread */                      
1001                         pthread_create( qry->thread, NULL,
1002                                 (void *) ldapqry_search, (void *) qry );
1003                 }
1004         }
1005         return ADDRQUERY_RETVAL(qry);
1006 }
1007
1008 /**
1009  * Cleanup LDAP thread data. This function will be called when each thread
1010  * exits. Note that the thread object will be freed by the kernel.
1011  * \param ptr Pointer to object being destroyed (a query object in this case).
1012  */
1013 static void ldapqry_destroyer( void * ptr ) {
1014         LdapQuery *qry;
1015
1016         qry = ( LdapQuery * ) ptr;
1017         g_return_if_fail( qry != NULL );
1018
1019         debug_print("ldapqry_destroyer::%d::%s\n", (int) pthread_self(),
1020                         ADDRQUERY_NAME(qry)?ADDRQUERY_NAME(qry):"null");
1021
1022         /* Perform any destruction here */
1023         if( qry->control != NULL ) {
1024                 ldapctl_free( qry->control );
1025         }
1026         qry->control = NULL;
1027         qry->thread = NULL;
1028         ldapqry_set_busy_flag( qry, FALSE );
1029 }
1030
1031 /**
1032  * Cancel thread associated with query.
1033  * \param qry Query object to process.
1034  */
1035 void ldapqry_cancel( LdapQuery *qry ) {
1036         g_return_if_fail( qry != NULL );
1037
1038         debug_print("cancelling::%d::%s\n", (int) pthread_self(),
1039                         ADDRQUERY_NAME(qry)?ADDRQUERY_NAME(qry):"null");
1040         if( ldapqry_get_busy_flag( qry ) ) {
1041                 if( qry->thread ) {
1042                         debug_print("calling pthread_cancel\n");
1043                         pthread_cancel( * qry->thread );
1044                 }
1045         }
1046 }
1047
1048 /**
1049  * Initialize LDAP query. This function should be called once before executing
1050  * any LDAP queries to initialize thread specific data.
1051  */
1052 void ldapqry_initialize( void ) {
1053         debug_print("ldapqry_initialize...\n");
1054         if( ! _queryThreadInit_ ) {
1055                 debug_print("ldapqry_initialize::creating thread specific area\n");
1056                 pthread_key_create( &_queryThreadKey_, ldapqry_destroyer );
1057                 _queryThreadInit_ = TRUE;
1058         }
1059         debug_print("ldapqry_initialize... done!\n");
1060 }
1061
1062 /**
1063  * Age the query based on LDAP control parameters.
1064  * \param qry    Query object to process.
1065  * \param maxAge Maximum age of query (in seconds).
1066  */
1067 void ldapqry_age( LdapQuery *qry, gint maxAge ) {
1068         gint age;
1069
1070         g_return_if_fail( qry != NULL );
1071
1072         /* Limit the time that queries can hang around */       
1073         if( maxAge < 1 ) maxAge = LDAPCTL_MAX_QUERY_AGE;
1074
1075         /* Check age of query */
1076         age = time( NULL ) - qry->touchTime;
1077         if( age > maxAge ) {
1078                 qry->agedFlag = TRUE;
1079         }
1080 }
1081
1082 /**
1083  * Delete folder associated with query results.
1084  * \param qry Query object to process.
1085  */
1086 void ldapqry_delete_folder( LdapQuery *qry ) {
1087         AddressCache *cache;
1088         ItemFolder *folder;
1089
1090         g_return_if_fail( qry != NULL );
1091
1092         folder = ADDRQUERY_FOLDER(qry);
1093         if( folder ) {
1094                 cache = qry->server->addressCache;
1095                 folder = addrcache_remove_folder_delete( cache, folder );
1096                 if( folder ) {
1097                         addritem_free_item_folder( folder );
1098                 }
1099                 ADDRQUERY_FOLDER(qry) = NULL;
1100         }
1101 }
1102
1103 /**
1104  * Create a name/value pair object.
1105  * \param n Name.
1106  * \param v Value.
1107  * \return Initialized object.
1108  */
1109 static NameValuePair *ldapqry_create_name_value( const gchar *n, const gchar *v ) {
1110         NameValuePair *nvp = g_new0( NameValuePair, 1 );
1111
1112         nvp->name = g_strdup( n );
1113         nvp->value = g_strdup( v );
1114         return nvp;
1115 }
1116
1117 /**
1118  * Free up name/value pair object.
1119  * \param nvp Name/value object.
1120  */
1121 void ldapqry_free_name_value( NameValuePair *nvp ) {
1122         if( nvp ) {
1123                 g_free( nvp->name );
1124                 g_free( nvp->value );
1125                 nvp->name = nvp->value = NULL;
1126                 g_free( nvp );
1127         }
1128 }
1129
1130 /**
1131  * Free up a list name/value pair objects.
1132  * \param list List of name/value objects.
1133  */
1134 void ldapqry_free_list_name_value( GList *list ) {
1135         GList *node;
1136
1137         node = list;
1138         while( node ) {
1139                 NameValuePair *nvp = ( NameValuePair * ) node->data;
1140                 ldapqry_free_name_value( nvp );
1141                 node->data = NULL;
1142                 node = g_list_next( node );
1143         }
1144         g_list_free( list );
1145 }
1146
1147 /**
1148  * Load a list of name/value pairs from LDAP attributes.
1149  * \param  ld          LDAP handle.
1150  * \param  e          LDAP message.
1151  * \param  attr       Attribute name.
1152  * \param  listValues List to populate.
1153  * \return List of attribute name/value pairs.
1154  */
1155 static GList *ldapqry_load_attrib_values(
1156                 LDAP *ld, LDAPMessage *entry, char *attr,
1157                 GList *listValues )
1158 {
1159         GList *list = NULL;
1160         gint i;
1161         struct berval **vals;
1162         NameValuePair *nvp;
1163
1164         list = listValues;
1165         if( ( vals = ldap_get_values_len( ld, entry, attr ) ) != NULL ) {
1166                 for( i = 0; vals[i] != NULL; i++ ) {
1167                         gchar *tmp = g_strndup( vals[i]->bv_val, vals[i]->bv_len);
1168                         nvp = ldapqry_create_name_value( attr, tmp );
1169                         g_free(tmp);
1170                         list = g_list_append( list, nvp );
1171                 }
1172         }
1173         ldap_value_free_len( vals );
1174         return list;
1175 }
1176
1177 /**
1178  * Fetch a list of all attributes.
1179  * \param  ld    LDAP handle.
1180  * \param  e     LDAP message.
1181  * \return List of attribute name/value pairs.
1182  */
1183 static GList *ldapqry_fetch_attribs( LDAP *ld, LDAPMessage *e )
1184 {
1185         char *attribute;
1186         BerElement *ber;
1187         GList *listValues = NULL;
1188
1189         /* Process all attributes */
1190         for( attribute = ldap_first_attribute( ld, e, &ber ); attribute != NULL;
1191                 attribute = ldap_next_attribute( ld, e, ber ) ) {
1192                 listValues = ldapqry_load_attrib_values( ld, e, attribute, listValues );
1193                 ldap_memfree( attribute );
1194         }
1195
1196         /* Free up */
1197         if( ber != NULL ) {
1198                 ber_free( ber, 0 );
1199         }
1200         return listValues;
1201 }
1202
1203 #define CRITERIA_SINGLE "(objectclass=*)"
1204
1205 /**
1206  * Perform the data retrieval for a specific LDAP record.
1207  * 
1208  * \param  qry Query object to process.
1209  * \return Error/status code.
1210  */
1211 static gint ldapqry_locate_retrieve( LdapQuery *qry ) {
1212         LdapControl *ctl;
1213         LDAP *ld;
1214         LDAPMessage *result, *e = NULL;
1215         gboolean entriesFound;
1216         gboolean first;
1217         struct timeval timeout;
1218         gint rc;
1219         gchar *dn;
1220         GList *listValues;
1221
1222         /* Initialize some variables */
1223         ld = qry->ldap;
1224         ctl = qry->control;
1225         dn = ADDRQUERY_SEARCHVALUE(qry);
1226         timeout.tv_sec = ctl->timeOut;
1227         timeout.tv_usec = 0L;
1228         entriesFound = FALSE;
1229
1230         /*
1231          * Execute the search - this step may take some time to complete
1232          * depending on network traffic and server response time.
1233          */
1234         ADDRQUERY_RETVAL(qry) = LDAPRC_TIMEOUT;
1235         rc = ldap_search_ext_s( ld, dn, LDAP_SCOPE_BASE, CRITERIA_SINGLE,
1236                 NULL, 0, NULL, NULL, &timeout, 0, &result );
1237         if( rc == LDAP_TIMEOUT ) {
1238                 return ADDRQUERY_RETVAL(qry);
1239         }
1240         ADDRQUERY_RETVAL(qry) = LDAPRC_SEARCH;
1241         if( rc != LDAP_SUCCESS ) {
1242                 debug_print("LDAP Error: ldap_search_st: %s\n", ldap_err2string(rc));
1243                 return ADDRQUERY_RETVAL(qry);
1244         }
1245
1246         debug_print("Total results are: %d\n", ldap_count_entries(ld, result));
1247
1248         /* Process results */
1249         ADDRQUERY_RETVAL(qry) = LDAPRC_STOP_FLAG;
1250         first = TRUE;
1251         while( TRUE ) {
1252                 ldapqry_touch( qry );
1253                 if( qry->entriesRead >= ctl->maxEntries ) break;                
1254
1255                 /* Test for stop */
1256                 if( ldapqry_get_stop_flag( qry ) ) {
1257                         break;
1258                 }
1259
1260                 /* Retrieve entry */            
1261                 if( first ) {
1262                         first = FALSE;
1263                         e = ldap_first_entry( ld, result );
1264                 }
1265                 else {
1266                         e = ldap_next_entry( ld, e );
1267                 }
1268                 if( e == NULL ) break;
1269
1270                 entriesFound = TRUE;
1271
1272                 /* Setup a critical section here */
1273                 pthread_mutex_lock( qry->mutexEntry );
1274
1275                 /* Process entry */
1276                 listValues = ldapqry_fetch_attribs( ld, e );
1277
1278                 /* Process callback */
1279                 if( qry->callBackEntry ) {
1280                         qry->callBackEntry( qry, ADDRQUERY_ID(qry), listValues, qry->data );
1281                 }
1282                 ldapqry_free_list_name_value( listValues );
1283                 listValues = NULL;
1284
1285                 pthread_mutex_unlock( qry->mutexEntry );
1286         }
1287
1288         /* Free up and disconnect */
1289         ldap_msgfree( result );
1290
1291         if( entriesFound ) {
1292                 ADDRQUERY_RETVAL(qry) = LDAPRC_SUCCESS;
1293         }
1294         else {
1295                 ADDRQUERY_RETVAL(qry) = LDAPRC_NOENTRIES;
1296         }
1297
1298         return ADDRQUERY_RETVAL(qry);
1299 }
1300
1301 /**
1302  * Perform the search to locate a specific LDAP record identified by
1303  * distinguished name (dn).
1304  * 
1305  * \param  qry Query object to process.
1306  * \return Error/status code.
1307  */
1308 static gint ldapqry_perform_locate( LdapQuery *qry ) {
1309         /* Connect */
1310         qry->ldap = NULL;
1311         ldapqry_connect( qry );
1312         if( ADDRQUERY_RETVAL(qry) == LDAPRC_SUCCESS ) {
1313                 /* Perform search */
1314                 ldapqry_locate_retrieve( qry );
1315         }
1316         /* Disconnect */
1317         ldapqry_disconnect( qry );
1318         qry->ldap = NULL;
1319
1320         /* Process callback */  
1321         if( qry->callBackEnd ) {
1322                 g_timeout_add(0, callbackend, qry);
1323         }
1324
1325         return ADDRQUERY_RETVAL(qry);
1326 }
1327
1328 /**
1329  * Remove results (folder and data) for specified LDAP query.
1330  * \param  qry Query object to process.
1331  * \return TRUE if folder deleted successfully.
1332  */
1333 gboolean ldapquery_remove_results( LdapQuery *qry ) {
1334         gboolean retVal = FALSE;
1335
1336         ldapqry_set_aged_flag( qry, TRUE );
1337
1338         if( ldapqry_get_busy_flag( qry ) ) {
1339                 ldapqry_set_stop_flag( qry, TRUE );
1340         }
1341         else {
1342                 LdapServer *server = qry->server;
1343                 server->listQuery = g_list_remove(server->listQuery, qry);
1344
1345                 retVal = TRUE;
1346         }
1347         return retVal;
1348 }
1349
1350 void ldapqry_print(LdapQuery *qry, FILE *stream) {
1351         g_return_if_fail( qry != NULL );
1352
1353         ldapsvr_print_data(qry->server, stream);
1354         ldapctl_print(qry->control, stream);
1355         fprintf(stream, "entriesRead: %d\n", qry->entriesRead);
1356         fprintf(stream, "elapsedTime: %d\n", qry->elapsedTime);
1357         fprintf(stream, "stopFlag: %d\n", qry->stopFlag);
1358         fprintf(stream, "busyFlag: %d\n", qry->busyFlag);
1359         fprintf(stream, "agedFlag: %d\n", qry->agedFlag);
1360         fprintf(stream, "completed: %d\n", qry->completed);
1361         fprintf(stream, "startTime: %d\n", (int) qry->startTime);
1362         fprintf(stream, "touchTime: %d\n", (int) qry->touchTime);
1363         fprintf(stream, "data: %s\n", qry->data?(gchar *)qry->data:"null");
1364 }
1365
1366 #endif  /* USE_LDAP */
1367
1368 /*
1369  * End of Source.
1370  */
1371
1372