2005-02-18 [colin] 1.0.1cvs14.1
[claws.git] / src / ldapctrl.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2003-2004 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 for LDAP control data.
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
34 #include "ldapctrl.h"
35 #include "mgutils.h"
36
37 /**
38  * Create new LDAP control block object.
39  * \return Initialized control object.
40  */
41 LdapControl *ldapctl_create( void ) {
42         LdapControl *ctl;
43
44         ctl = g_new0( LdapControl, 1 );
45         ctl->hostName = NULL;
46         ctl->port = LDAPCTL_DFL_PORT;
47         ctl->baseDN = NULL;
48         ctl->bindDN = NULL;
49         ctl->bindPass = NULL;
50         ctl->listCriteria = NULL;
51         ctl->attribEMail = g_strdup( LDAPCTL_ATTR_EMAIL );
52         ctl->attribCName = g_strdup( LDAPCTL_ATTR_COMMONNAME );
53         ctl->attribFName = g_strdup( LDAPCTL_ATTR_GIVENNAME );
54         ctl->attribLName = g_strdup( LDAPCTL_ATTR_SURNAME );
55         ctl->maxEntries = LDAPCTL_MAX_ENTRIES;
56         ctl->timeOut = LDAPCTL_DFL_TIMEOUT;
57         ctl->maxQueryAge = LDAPCTL_DFL_QUERY_AGE;
58         ctl->matchingOption = LDAPCTL_MATCH_BEGINWITH;
59         ctl->version = 0;
60         ctl->enableTLS = FALSE;
61
62         /* Mutex to protect control block */
63         ctl->mutexCtl = g_malloc0( sizeof( pthread_mutex_t ) );
64         pthread_mutex_init( ctl->mutexCtl, NULL );
65
66         return ctl;
67 }
68
69 /**
70  * Specify hostname to be used.
71  * \param ctl   Control object to process.
72  * \param value Host name.
73  */
74 void ldapctl_set_host( LdapControl* ctl, const gchar *value ) {
75         ctl->hostName = mgu_replace_string( ctl->hostName, value );
76         g_strstrip( ctl->hostName );
77 }
78
79 /**
80  * Specify port to be used.
81  * \param ctl  Control object to process.
82  * \param value Port.
83  */
84 void ldapctl_set_port( LdapControl* ctl, const gint value ) {
85         if( value > 0 ) {
86                 ctl->port = value;
87         }
88         else {
89                 ctl->port = LDAPCTL_DFL_PORT;
90         }
91 }
92
93 /**
94  * Specify base DN to be used.
95  * \param ctl  Control object to process.
96  * \param value Base DN.
97  */
98 void ldapctl_set_base_dn( LdapControl* ctl, const gchar *value ) {
99         ctl->baseDN = mgu_replace_string( ctl->baseDN, value );
100         g_strstrip( ctl->baseDN );
101 }
102
103 /**
104  * Specify bind DN to be used.
105  * \param ctl  Control object to process.
106  * \param value Bind DN.
107  */
108 void ldapctl_set_bind_dn( LdapControl* ctl, const gchar *value ) {
109         ctl->bindDN = mgu_replace_string( ctl->bindDN, value );
110         g_strstrip( ctl->bindDN );
111 }
112
113 /**
114  * Specify bind password to be used.
115  * \param ctl  Control object to process.
116  * \param value Password.
117  */
118 void ldapctl_set_bind_password( LdapControl* ctl, const gchar *value ) {
119         ctl->bindPass = mgu_replace_string( ctl->bindPass, value );
120         g_strstrip( ctl->bindPass );
121 }
122
123 /**
124  * Specify maximum number of entries to retrieve.
125  * \param ctl  Control object to process.
126  * \param value Maximum entries.
127  */
128 void ldapctl_set_max_entries( LdapControl* ctl, const gint value ) {
129         if( value > 0 ) {
130                 ctl->maxEntries = value;
131         }
132         else {
133                 ctl->maxEntries = LDAPCTL_MAX_ENTRIES;
134         }
135 }
136
137 /**
138  * Specify timeout value for LDAP operation (in seconds).
139  * \param ctl  Control object to process.
140  * \param value Timeout.
141  */
142 void ldapctl_set_timeout( LdapControl* ctl, const gint value ) {
143         if( value > 0 ) {
144                 ctl->timeOut = value;
145         }
146         else {
147                 ctl->timeOut = LDAPCTL_DFL_TIMEOUT;
148         }
149 }
150
151 /**
152  * Specify maximum age of query (in seconds) before query is retired.
153  * \param ctl  Control object to process.
154  * \param value Maximum age.
155  */
156 void ldapctl_set_max_query_age( LdapControl* ctl, const gint value ) {
157         if( value > LDAPCTL_MAX_QUERY_AGE ) {
158                 ctl->maxQueryAge = LDAPCTL_MAX_QUERY_AGE;
159         }
160         else if( value < 1 ) {
161                 ctl->maxQueryAge = LDAPCTL_DFL_QUERY_AGE;
162         }
163         else {
164                 ctl->maxQueryAge = value;
165         }
166 }
167
168 /**
169  * Specify matching option to be used for searches.
170  * \param ctl   Control object to process.
171  * \param value Matching option, as follows:
172  * <ul>
173  * <li><code>LDAPCTL_MATCH_BEGINWITH</code> for "begins with" search</li>
174  * <li><code>LDAPCTL_MATCH_CONTAINS</code> for "contains" search</li>
175  * </ul>
176  */
177 void ldapctl_set_matching_option( LdapControl* ctl, const gint value ) {
178         if( value < LDAPCTL_MATCH_BEGINWITH ) {
179                 ctl->matchingOption = LDAPCTL_MATCH_BEGINWITH;
180         }
181         else if( value > LDAPCTL_MATCH_CONTAINS ) {
182                 ctl->matchingOption = LDAPCTL_MATCH_BEGINWITH;
183         }
184         else {
185                 ctl->matchingOption = value;
186         }
187 }
188
189 /**
190  * Specify TLS option.
191  * \param ctl   Control object to process.
192  * \param value <i>TRUE</i> to enable TLS.
193  */
194 void ldapctl_set_tls( LdapControl* ctl, const gboolean value ) {
195         ctl->enableTLS = value;
196 }
197
198 /**
199  * Specify search criteria list to be used.
200  * \param ctl   Control data object.
201  * \param value Linked list of LDAP attribute names to use for search.
202  */
203 void ldapctl_set_criteria_list( LdapControl* ctl, GList *value ) {
204         g_return_if_fail( ctl != NULL );
205         mgu_free_dlist( ctl->listCriteria );
206         ctl->listCriteria = value;
207 }
208
209 /**
210  * Return search criteria list.
211  * \param  ctl  Control data object.
212  * \return Linked list of character strings containing LDAP attribute names to
213  *         use for a search. This should not be modified directly. Use the
214  *         <code>ldapctl_set_criteria_list()</code>,
215  *         <code>ldapctl_criteria_list_clear()</code> and
216  *         <code>ldapctl_criteria_list_add()</code> functions for this purpose.
217  */
218 GList *ldapctl_get_criteria_list( const LdapControl* ctl ) {
219         g_return_val_if_fail( ctl != NULL, NULL );
220         return ctl->listCriteria;
221 }
222
223 /**
224  * Clear list of LDAP search attributes.
225  * \param  ctl  Control data object.
226  */
227 void ldapctl_criteria_list_clear( LdapControl *ctl ) {
228         g_return_if_fail( ctl != NULL );
229         mgu_free_dlist( ctl->listCriteria );
230         ctl->listCriteria = NULL;
231 }
232
233 /**
234  * Add LDAP attribute to criteria list.
235  * \param ctl  Control object to process.
236  * \param attr Attribute name to append. If not NULL and unique, a copy will
237  *             be appended to the list.
238  */
239 void ldapctl_criteria_list_add( LdapControl *ctl, gchar *attr ) {
240         g_return_if_fail( ctl != NULL );
241         if( attr != NULL ) {
242                 if( mgu_list_test_unq_nc( ctl->listCriteria, attr ) ) {
243                         ctl->listCriteria = g_list_append(
244                                 ctl->listCriteria, g_strdup( attr ) );
245                 }
246         }
247 }
248
249 /**
250  * Build criteria list using default attributes.
251  * \param ctl  Control object to process.
252  */
253 void ldapctl_default_attributes( LdapControl *ctl ) {
254         g_return_if_fail( ctl != NULL );
255
256         ldapctl_criteria_list_clear( ctl );
257         ldapctl_criteria_list_add( ctl, LDAPCTL_ATTR_COMMONNAME );
258         ldapctl_criteria_list_add( ctl, LDAPCTL_ATTR_GIVENNAME );
259         ldapctl_criteria_list_add( ctl, LDAPCTL_ATTR_SURNAME );
260         ldapctl_criteria_list_add( ctl, LDAPCTL_ATTR_EMAIL );
261 }
262
263 /**
264  * Clear LDAP server member variables.
265  * \param ctl Control object to clear.
266  */
267 void ldapctl_clear( LdapControl *ctl ) {
268         g_return_if_fail( ctl != NULL );
269
270         /* Free internal stuff */
271         g_free( ctl->hostName );
272         g_free( ctl->baseDN );
273         g_free( ctl->bindDN );
274         g_free( ctl->bindPass );
275         g_free( ctl->attribEMail );
276         g_free( ctl->attribCName );
277         g_free( ctl->attribFName );
278         g_free( ctl->attribLName );
279
280         ldapctl_criteria_list_clear( ctl );
281
282         /* Clear pointers */
283         ctl->hostName = NULL;
284         ctl->port = 0;
285         ctl->baseDN = NULL;
286         ctl->bindDN = NULL;
287         ctl->bindPass = NULL;
288         ctl->attribEMail = NULL;
289         ctl->attribCName = NULL;
290         ctl->attribFName = NULL;
291         ctl->attribLName = NULL;
292         ctl->maxEntries = 0;
293         ctl->timeOut = 0;
294         ctl->maxQueryAge = 0;
295         ctl->matchingOption = LDAPCTL_MATCH_BEGINWITH;
296         ctl->version = 0;
297         ctl->enableTLS = FALSE;
298 }
299
300 /**
301  * Free up LDAP server interface object by releasing internal memory.
302  * \param ctl Control object to free.
303  */
304 void ldapctl_free( LdapControl *ctl ) {
305         g_return_if_fail( ctl != NULL );
306
307         /* Free internal stuff */
308         ldapctl_clear( ctl );
309
310         /* Free the mutex */
311         pthread_mutex_destroy( ctl->mutexCtl );
312         g_free( ctl->mutexCtl );
313         ctl->mutexCtl = NULL;
314
315         /* Now release LDAP control object */
316         g_free( ctl );
317 }
318
319 /**
320  * Setup default (empty) values for specified object.
321  * \param ctl  Control object to process.
322  */
323 void ldapctl_default_values( LdapControl *ctl ) {
324         g_return_if_fail( ctl != NULL );
325
326         /* Clear our destination */
327         ldapctl_clear( ctl );
328
329         /* Copy strings */
330         ctl->hostName = g_strdup( "" );
331         ctl->baseDN = g_strdup( "" );
332         ctl->bindDN = g_strdup( "" );
333         ctl->bindPass = g_strdup( "" );
334         ctl->port = LDAPCTL_DFL_PORT;
335         ctl->maxEntries = LDAPCTL_MAX_ENTRIES;
336         ctl->timeOut = LDAPCTL_DFL_TIMEOUT;
337         ctl->maxQueryAge = LDAPCTL_DFL_QUERY_AGE;
338         ctl->matchingOption = LDAPCTL_MATCH_BEGINWITH;
339         ctl->version = 0;
340         ctl->enableTLS = FALSE;
341
342         ldapctl_default_attributes( ctl );
343 }
344
345 /**
346  * Display object to specified stream.
347  * \param ctl    Control object to process.
348  * \param stream Output stream.
349  */
350 void ldapctl_print( const LdapControl *ctl, FILE *stream ) {
351         g_return_if_fail( ctl != NULL );
352
353         pthread_mutex_lock( ctl->mutexCtl );
354         fprintf( stream, "LdapControl:\n" );
355         fprintf( stream, "host name: '%s'\n", ctl->hostName );
356         fprintf( stream, "     port: %d\n",   ctl->port );
357         fprintf( stream, "  base dn: '%s'\n", ctl->baseDN );
358         fprintf( stream, "  bind dn: '%s'\n", ctl->bindDN );
359         fprintf( stream, "bind pass: '%s'\n", ctl->bindPass );
360         fprintf( stream, "attr mail: '%s'\n", ctl->attribEMail );
361         fprintf( stream, "attr comn: '%s'\n", ctl->attribCName );
362         fprintf( stream, "attr frst: '%s'\n", ctl->attribFName );
363         fprintf( stream, "attr last: '%s'\n", ctl->attribLName );
364         fprintf( stream, "max entry: %d\n",   ctl->maxEntries );
365         fprintf( stream, "  timeout: %d\n",   ctl->timeOut );
366         fprintf( stream, "  max age: %d\n",   ctl->maxQueryAge );
367         fprintf( stream, "match opt: %d\n",   ctl->matchingOption );
368         fprintf( stream, "  version: %d\n",   ctl->version );
369         fprintf( stream, "      TLS: %s\n",   ctl->enableTLS ? "yes" : "no" );
370         fprintf( stream, "crit list:\n" );
371         if( ctl->listCriteria ) {
372                 mgu_print_dlist( ctl->listCriteria, stream );
373         }
374         else {
375                 fprintf( stream, "\t!!!none!!!\n" );
376         }
377         pthread_mutex_unlock( ctl->mutexCtl );
378 }
379
380 /**
381  * Copy member variables to specified object. Mutex lock object is
382  * not copied.
383  * \param ctlFrom Object to copy from.
384  * \param ctlTo   Destination object.
385  */
386 void ldapctl_copy( const LdapControl *ctlFrom, LdapControl *ctlTo ) {
387         GList *node;
388
389         g_return_if_fail( ctlFrom != NULL );
390         g_return_if_fail( ctlTo != NULL );
391
392         /* Lock both objects */
393         pthread_mutex_lock( ctlFrom->mutexCtl );
394         pthread_mutex_lock( ctlTo->mutexCtl );
395
396         /* Clear our destination */
397         ldapctl_clear( ctlTo );
398
399         /* Copy strings */
400         ctlTo->hostName = g_strdup( ctlFrom->hostName );
401         ctlTo->baseDN = g_strdup( ctlFrom->baseDN );
402         ctlTo->bindDN = g_strdup( ctlFrom->bindDN );
403         ctlTo->bindPass = g_strdup( ctlFrom->bindPass );
404         ctlTo->attribEMail = g_strdup( ctlFrom->attribEMail );
405         ctlTo->attribCName = g_strdup( ctlFrom->attribCName );
406         ctlTo->attribFName = g_strdup( ctlFrom->attribFName );
407         ctlTo->attribLName = g_strdup( ctlFrom->attribLName );
408
409         /* Copy search criteria */
410         node = ctlFrom->listCriteria;
411         while( node ) {
412                 ctlTo->listCriteria = g_list_append(
413                         ctlTo->listCriteria, g_strdup( node->data ) );
414                 node = g_list_next( node );
415         }
416
417         /* Copy other members */
418         ctlTo->port = ctlFrom->port;
419         ctlTo->maxEntries = ctlFrom->maxEntries;
420         ctlTo->timeOut = ctlFrom->timeOut;
421         ctlTo->maxQueryAge = ctlFrom->maxQueryAge;
422         ctlTo->matchingOption = ctlFrom->matchingOption;
423         ctlTo->version = ctlFrom->version;
424         ctlTo->enableTLS = ctlFrom->enableTLS;
425
426         /* Unlock */
427         pthread_mutex_unlock( ctlTo->mutexCtl );
428         pthread_mutex_unlock( ctlFrom->mutexCtl );
429 }
430
431 /**
432  * Search criteria fragment - two terms - begin with (default).
433  */
434 static gchar *_criteria2BeginWith = "(&(givenName=%s*)(sn=%s*))";
435
436 /**
437  * Search criteria fragment - two terms - contains.
438  */
439 static gchar *_criteria2Contains  = "(&(givenName=*%s*)(sn=*%s*))";
440
441 /**
442  * Create an LDAP search criteria by parsing specified search term. The search
443  * term may contain two names separated by the first embedded space found in
444  * the search term. It is assumed that the two tokens are first name and last
445  * name, or vice versa. An appropriate search criteria will be constructed.
446  *
447  * \param  searchTerm   Reference to search term to process.
448  * \param  matchOption  Set to the following:
449  * <ul>
450  * <li><code>LDAPCTL_MATCH_BEGINWITH</code> for "begins with" search</li>
451  * <li><code>LDAPCTL_MATCH_CONTAINS</code> for "contains" search</li>
452  * </ul>
453  *
454  * \return Formatted search criteria, or <code>NULL</code> if there is no
455  *         embedded spaces. The search term should be g_free() when no
456  *         longer required.
457  */
458 static gchar *ldapctl_build_ldap_criteria(
459                 const gchar *searchTerm, const gint matchOption )
460 {
461         gchar *p;
462         gchar *t1;
463         gchar *t2 = NULL;
464         gchar *term;
465         gchar *crit = NULL;
466         gchar *criteriaFmt;
467
468         if( matchOption == LDAPCTL_MATCH_CONTAINS ) {
469                 criteriaFmt = _criteria2Contains;
470         }
471         else {
472                 criteriaFmt = _criteria2BeginWith;
473         }
474
475         term = g_strdup( searchTerm );
476         g_strstrip( term );
477
478         /* Find first space character */        
479         t1 = p = term;
480         while( *p ) {
481                 if( *p == ' ' ) {
482                         *p = '\0';
483                         t2 = g_strdup( 1 + p );
484                         break;
485                 }
486                 p++;
487         }
488
489         if( t2 ) {
490                 /* Format search criteria */
491                 gchar *p1, *p2;
492
493                 g_strstrip( t2 );
494                 p1 = g_strdup_printf( criteriaFmt, t1, t2 );
495                 p2 = g_strdup_printf( criteriaFmt, t2, t1 );
496                 crit = g_strdup_printf( "(&(|%s%s)(mail=*))", p1, p2 );
497
498                 g_free( t2 );
499                 g_free( p1 );
500                 g_free( p2 );
501         }
502         g_free( term );
503         return crit;
504 }
505
506
507 /**
508  * Search criteria fragment - single term - begin with (default).
509  */
510 static gchar *_criteriaBeginWith = "(%s=%s*)";
511
512 /**
513  * Search criteria fragment - single term - contains.
514  */
515 static gchar *_criteriaContains  = "(%s=*%s*)";
516
517 /**
518  * Build a formatted LDAP search criteria string from criteria list.
519  * \param ctl  Control object to process.
520  * \param searchVal Value to search for.
521  * \return Formatted string. Should be g_free() when done.
522  */
523 gchar *ldapctl_format_criteria( LdapControl *ctl, const gchar *searchVal ) {
524         GList *node;
525         gchar *p1, *p2, *retVal;
526         gchar *criteriaFmt;
527
528         g_return_val_if_fail( ctl != NULL, NULL );
529         g_return_val_if_fail( searchVal != NULL, NULL );
530
531         /* Test whether there are more that one search terms */
532         retVal = ldapctl_build_ldap_criteria( searchVal, ctl->matchingOption );
533         if( retVal ) return retVal;
534
535         if( ctl->matchingOption ==  LDAPCTL_MATCH_CONTAINS ) {
536                 criteriaFmt = _criteriaContains;
537         }
538         else {
539                 criteriaFmt = _criteriaBeginWith;
540         }
541
542         /* No - just a simple search */
543         /* p1 contains previous formatted criteria */
544         /* p2 contains next formatted criteria */
545         retVal = p1 = p2 = NULL;
546         node = ctl->listCriteria;
547         while( node ) {
548                 gchar *attr, *tmp;
549
550                 attr = node->data;
551                 node = g_list_next( node );
552
553                 /* Switch pointers */
554                 tmp = p1; p1 = p2; p2 = tmp;
555
556                 if( p1 ) {
557                         /* Subsequent time through */
558                         gchar *crit;
559
560                         /* Format query criteria */
561                         crit = g_strdup_printf( criteriaFmt, attr, searchVal );
562
563                         /* Append to existing criteria */                       
564                         g_free( p2 );
565                         p2 = g_strdup_printf( "(|%s%s)", p1, crit );
566
567                         g_free( crit );
568                 }
569                 else {
570                         /* First time through - Format query criteria */
571                         p2 = g_strdup_printf( criteriaFmt, attr, searchVal );
572                 }
573         }
574
575         if( p2 == NULL ) {
576                 /* Nothing processed - format a default attribute */
577                 retVal = g_strdup_printf( "(%s=*)", LDAPCTL_ATTR_EMAIL );
578         }
579         else {
580                 /* We have something - free up previous result */
581                 retVal = p2;
582                 g_free( p1 );
583         }
584         return retVal;
585 }
586
587 /**
588  * Return array of pointers to attributes for LDAP query.
589  * \param  ctl  Control object to process.
590  * \return NULL terminated list.
591  */
592 char **ldapctl_attribute_array( LdapControl *ctl ) {
593         char **ptrArray;
594         GList *node;
595         gint cnt, i;
596         g_return_val_if_fail( ctl != NULL, NULL );
597
598         cnt = g_list_length( ctl->listCriteria );
599         ptrArray = g_new0( char *, 1 + cnt );
600         i = 0;
601         node = ctl->listCriteria;
602         while( node ) {
603                 ptrArray[ i++ ] = node->data;
604                 node = g_list_next( node );
605         }
606         ptrArray[ i ] = NULL;
607         return ptrArray;
608 }
609
610 /**
611  * Free array of pointers allocated by ldapctl_criteria_array().
612  * param ptrArray Array to clear.
613  */
614 void ldapctl_free_attribute_array( char **ptrArray ) {
615         gint i;
616
617         /* Clear array to NULL's */
618         for( i = 0; ptrArray[i] != NULL; i++ ) {
619                 ptrArray[i] = NULL;
620         }
621         g_free( ptrArray );
622 }       
623
624 /**
625  * Parse LDAP search string, building list of LDAP criteria attributes. This
626  * may be used to convert an old style Sylpheed LDAP search criteria to the
627  * new format. The old style uses a standard LDAP search string, for example:
628  * <pre>
629  *    (&(mail=*)(cn=%s*))
630  * </pre>
631  * This function extracts the two LDAP attributes <code>mail</code> and
632  * <code>cn</code>, adding each to a list.
633  *
634  * \param ctl Control object to process.
635  * \param criteria LDAP search criteria string.
636  */
637 void ldapctl_parse_ldap_search( LdapControl *ctl, gchar *criteria ) {
638         gchar *ptr;
639         gchar *pFrom;
640         gchar *attrib;
641         gint iLen;
642
643         g_return_if_fail( ctl != NULL );
644
645         ldapctl_criteria_list_clear( ctl );
646         if( criteria == NULL ) return;
647
648         pFrom = NULL;
649         ptr = criteria;
650         while( *ptr ) {
651                 if( *ptr == '(' ) {
652                         pFrom = 1 + ptr;
653                 }
654                 if( *ptr == '=' ) {
655                         if( pFrom ) {
656                                 iLen = ptr - pFrom;
657                                 attrib = g_strndup( pFrom, iLen );
658                                 g_strstrip( attrib );
659                                 ldapctl_criteria_list_add( ctl, attrib );
660                                 g_free( attrib );
661                         }
662                         pFrom = NULL;
663                 }
664                 ptr++;
665         }
666 }
667
668 #endif  /* USE_LDAP */
669
670 /*
671  * End of Source.
672  */
673