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