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