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