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