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