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