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