fix CID 1596595: Resource leaks, and CID 1596594: (CHECKED_RETURN)
[claws.git] / src / ldapctrl.c
1 /*
2  * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3  * Copyright (C) 2003-2022 the Claws Mail team and 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 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 /*
20  * Functions for LDAP control data.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #include "claws-features.h"
26 #endif
27
28 #ifdef USE_LDAP
29
30 #include <glib.h>
31 #include <sys/time.h>
32 #include <string.h>
33
34 #include "ldapctrl.h"
35 #include "mgutils.h"
36 #include "passwordstore.h"
37 #include "editaddress_other_attributes_ldap.h"
38 #include "common/utils.h"
39 #include "common/quoted-printable.h"
40
41 /**
42  * Create new LDAP control block object.
43  * \return Initialized control object.
44  */
45 LdapControl *ldapctl_create( void ) {
46         LdapControl *ctl;
47
48         ctl = g_new0( LdapControl, 1 );
49         ctl->hostName = NULL;
50         ctl->port = LDAPCTL_DFL_PORT;
51         ctl->baseDN = NULL;
52         ctl->bindDN = NULL;
53         ctl->listCriteria = NULL;
54         ctl->attribEMail = g_strdup( LDAPCTL_ATTR_EMAIL );
55         ctl->attribCName = g_strdup( LDAPCTL_ATTR_COMMONNAME );
56         ctl->attribFName = g_strdup( LDAPCTL_ATTR_GIVENNAME );
57         ctl->attribLName = g_strdup( LDAPCTL_ATTR_SURNAME );
58         ctl->attribDName = g_strdup( LDAPCTL_ATTR_DISPLAYNAME );
59         ctl->maxEntries = LDAPCTL_MAX_ENTRIES;
60         ctl->timeOut = LDAPCTL_DFL_TIMEOUT;
61         ctl->maxQueryAge = LDAPCTL_DFL_QUERY_AGE;
62         ctl->matchingOption = LDAPCTL_MATCH_BEGINWITH;
63         ctl->version = 0;
64         ctl->enableTLS = FALSE;
65         ctl->enableSSL = FALSE;
66
67         /* Mutex to protect control block */
68         ctl->mutexCtl = g_malloc0( sizeof( pthread_mutex_t ) );
69         pthread_mutex_init( ctl->mutexCtl, NULL );
70
71         return ctl;
72 }
73
74 /**
75  * Specify hostname to be used.
76  * \param ctl   Control object to process.
77  * \param value Host name.
78  */
79 void ldapctl_set_host( LdapControl* ctl, const gchar *value ) {
80         ctl->hostName = mgu_replace_string( ctl->hostName, value );
81
82         if ( ctl->hostName == NULL )
83                 return;
84
85         g_strstrip( ctl->hostName );
86         debug_print("setting hostname: %s\n", ctl->hostName);
87 }
88
89 /**
90  * Specify port to be used.
91  * \param ctl  Control object to process.
92  * \param value Port.
93  */
94 void ldapctl_set_port( LdapControl* ctl, const gint value ) {
95         if( value > 0 ) {
96                 ctl->port = value;
97         }
98         else {
99                 ctl->port = LDAPCTL_DFL_PORT;
100         }
101         debug_print("setting port: %d\n", ctl->port);
102 }
103
104 /**
105  * Specify base DN to be used.
106  * \param ctl  Control object to process.
107  * \param value Base DN.
108  */
109 void ldapctl_set_base_dn( LdapControl* ctl, const gchar *value ) {
110         ctl->baseDN = mgu_replace_string( ctl->baseDN, value );
111
112         if ( ctl->baseDN == NULL )
113                 return;
114
115         g_strstrip( ctl->baseDN );
116         debug_print("setting baseDN: %s\n", ctl->baseDN);
117 }
118
119 /**
120  * Specify bind DN to be used.
121  * \param ctl  Control object to process.
122  * \param value Bind DN.
123  */
124 void ldapctl_set_bind_dn( LdapControl* ctl, const gchar *value ) {
125         ctl->bindDN = mgu_replace_string( ctl->bindDN, value );
126
127         if ( ctl->bindDN == NULL )
128                 return;
129
130         g_strstrip( ctl->bindDN );
131         debug_print("setting bindDN: %s\n", ctl->bindDN);
132 }
133
134 /**
135  * Specify maximum number of entries to retrieve.
136  * \param ctl  Control object to process.
137  * \param value Maximum entries.
138  */
139 void ldapctl_set_max_entries( LdapControl* ctl, const gint value ) {
140         if( value > 0 ) {
141                 ctl->maxEntries = value;
142         }
143         else {
144                 ctl->maxEntries = LDAPCTL_MAX_ENTRIES;
145         }
146         debug_print("setting maxEntries: %d\n", ctl->maxEntries);
147 }
148
149 /**
150  * Specify timeout value for LDAP operation (in seconds).
151  * \param ctl  Control object to process.
152  * \param value Timeout.
153  */
154 void ldapctl_set_timeout( LdapControl* ctl, const gint value ) {
155         if( value > 0 ) {
156                 ctl->timeOut = value;
157         }
158         else {
159                 ctl->timeOut = LDAPCTL_DFL_TIMEOUT;
160         }
161         debug_print("setting timeOut: %d\n", ctl->timeOut);
162 }
163
164 /**
165  * Specify maximum age of query (in seconds) before query is retired.
166  * \param ctl  Control object to process.
167  * \param value Maximum age.
168  */
169 void ldapctl_set_max_query_age( LdapControl* ctl, const gint value ) {
170         if( value > LDAPCTL_MAX_QUERY_AGE ) {
171                 ctl->maxQueryAge = LDAPCTL_MAX_QUERY_AGE;
172         }
173         else if( value < 1 ) {
174                 ctl->maxQueryAge = LDAPCTL_DFL_QUERY_AGE;
175         }
176         else {
177                 ctl->maxQueryAge = value;
178         }
179         debug_print("setting maxAge: %d\n", ctl->maxQueryAge);
180 }
181
182 /**
183  * Specify matching option to be used for searches.
184  * \param ctl   Control object to process.
185  * \param value Matching option, as follows:
186  * <ul>
187  * <li><code>LDAPCTL_MATCH_BEGINWITH</code> for "begins with" search</li>
188  * <li><code>LDAPCTL_MATCH_CONTAINS</code> for "contains" search</li>
189  * </ul>
190  */
191 void ldapctl_set_matching_option( LdapControl* ctl, const gint value ) {
192         if( value < LDAPCTL_MATCH_BEGINWITH ) {
193                 ctl->matchingOption = LDAPCTL_MATCH_BEGINWITH;
194         }
195         else if( value > LDAPCTL_MATCH_CONTAINS ) {
196                 ctl->matchingOption = LDAPCTL_MATCH_BEGINWITH;
197         }
198         else {
199                 ctl->matchingOption = value;
200         }
201         debug_print("setting matchingOption: %d\n", ctl->matchingOption);
202 }
203
204 /**
205  * Specify TLS option.
206  * \param ctl   Control object to process.
207  * \param value <i>TRUE</i> to enable TLS.
208  */
209 void ldapctl_set_tls( LdapControl* ctl, const gboolean value ) {
210 #if (defined USE_LDAP_TLS || defined G_OS_WIN32)
211         ctl->enableTLS = value;
212         debug_print("setting STARTTLS: %d\n", ctl->enableTLS);
213 #endif
214 }
215
216 void ldapctl_set_ssl( LdapControl* ctl, const gboolean value ) {
217 #if (defined USE_LDAP_TLS || defined G_OS_WIN32)
218         ctl->enableSSL = value;
219         debug_print("setting TLS: %d\n", ctl->enableSSL);
220 #endif
221 }
222
223 /**
224  * Return search criteria list.
225  * \param  ctl  Control data object.
226  * \return Linked list of character strings containing LDAP attribute names to
227  *         use for a search. This should not be modified directly. Use the
228  *         <code>ldapctl_set_criteria_list()</code>,
229  *         <code>ldapctl_criteria_list_clear()</code> and
230  *         <code>ldapctl_criteria_list_add()</code> functions for this purpose.
231  */
232 GList *ldapctl_get_criteria_list( const LdapControl* ctl ) {
233         cm_return_val_if_fail( ctl != NULL, NULL );
234         return ctl->listCriteria;
235 }
236
237 /**
238  * Clear list of LDAP search attributes.
239  * \param  ctl  Control data object.
240  */
241 void ldapctl_criteria_list_clear( LdapControl *ctl ) {
242         cm_return_if_fail( ctl != NULL );
243         g_list_free_full( ctl->listCriteria, g_free );
244         ctl->listCriteria = NULL;
245 }
246
247 /**
248  * Add LDAP attribute to criteria list.
249  * \param ctl  Control object to process.
250  * \param attr Attribute name to append. If not NULL and unique, a copy will
251  *             be appended to the list.
252  */
253 void ldapctl_criteria_list_add( LdapControl *ctl, gchar *attr ) {
254         cm_return_if_fail( ctl != NULL );
255         if( attr != NULL ) {
256                 if( !g_list_find_custom( ctl->listCriteria, attr,
257                                         (GCompareFunc)g_utf8_collate ) ) {
258                         debug_print("adding to criteria list: %s\n", attr);
259                         ctl->listCriteria = g_list_append(
260                                 ctl->listCriteria, g_strdup( attr ) );
261                 }
262         }
263 }
264
265 /**
266  * Clear LDAP server member variables.
267  * \param ctl Control object to clear.
268  */
269 static void ldapctl_clear( LdapControl *ctl ) {
270         cm_return_if_fail( ctl != NULL );
271
272         debug_print("clearing ldap controller members\n");
273         /* Free internal stuff */
274         g_free( ctl->hostName );
275         g_free( ctl->baseDN );
276         g_free( ctl->bindDN );
277         g_free( ctl->attribEMail );
278         g_free( ctl->attribCName );
279         g_free( ctl->attribFName );
280         g_free( ctl->attribLName );
281         g_free( ctl->attribDName );
282
283         ldapctl_criteria_list_clear( ctl );
284
285         /* Clear pointers */
286         ctl->hostName = NULL;
287         ctl->port = 0;
288         ctl->baseDN = NULL;
289         ctl->bindDN = NULL;
290         ctl->attribEMail = NULL;
291         ctl->attribCName = NULL;
292         ctl->attribFName = NULL;
293         ctl->attribLName = NULL;
294         ctl->attribDName = NULL;
295         ctl->maxEntries = 0;
296         ctl->timeOut = 0;
297         ctl->maxQueryAge = 0;
298         ctl->matchingOption = LDAPCTL_MATCH_BEGINWITH;
299         ctl->version = 0;
300         ctl->enableTLS = FALSE;
301         ctl->enableSSL = FALSE;
302 }
303
304 /**
305  * Free up LDAP server interface object by releasing internal memory.
306  * \param ctl Control object to free.
307  */
308 void ldapctl_free( LdapControl *ctl ) {
309         cm_return_if_fail( ctl != NULL );
310
311         debug_print("releasing requested memory for ldap controller\n");
312         /* Free internal stuff */
313         ldapctl_clear( ctl );
314
315         /* Free the mutex */
316         pthread_mutex_destroy( ctl->mutexCtl );
317         g_free( ctl->mutexCtl );
318         ctl->mutexCtl = NULL;
319
320         /* Now release LDAP control object */
321         g_free( ctl );
322 }
323
324 #ifdef DEBUG_LDAP
325 /**
326  * Display object to specified stream.
327  * \param ctl    Control object to process.
328  * \param stream Output stream.
329  */
330 void ldapctl_print( const LdapControl *ctl, FILE *stream ) {
331         cm_return_if_fail( ctl != NULL );
332         gchar *pwd;
333
334         pthread_mutex_lock( ctl->mutexCtl );
335         fprintf( stream, "LdapControl:\n" );
336         fprintf( stream, "host name: '%s'\n", ctl->hostName?ctl->hostName:"null" );
337         fprintf( stream, "     port: %d\n",   ctl->port );
338         fprintf( stream, "  base dn: '%s'\n", ctl->baseDN?ctl->baseDN:"null" );
339         fprintf( stream, "  bind dn: '%s'\n", ctl->bindDN?ctl->bindDN:"null" );
340         pwd = passwd_store_get(PWS_CORE, "LDAP", ctl->hostName);
341         fprintf( stream, "bind pass: '%s'\n", pwd?pwd:"null" );
342         if (pwd != NULL && strlen(pwd) > 0)
343                 memset(pwd, 0, strlen(pwd));
344         g_free(pwd);
345         fprintf( stream, "attr mail: '%s'\n", ctl->attribEMail?ctl->attribEMail:"null" );
346         fprintf( stream, "attr comn: '%s'\n", ctl->attribCName?ctl->attribCName:"null" );
347         fprintf( stream, "attr frst: '%s'\n", ctl->attribFName?ctl->attribFName:"null" );
348         fprintf( stream, "attr last: '%s'\n", ctl->attribLName?ctl->attribLName:"null" );
349         fprintf( stream, "attr disn: '%s'\n", ctl->attribDName?ctl->attribDName:"null" );
350         fprintf( stream, "max entry: %d\n",   ctl->maxEntries );
351         fprintf( stream, "  timeout: %d\n",   ctl->timeOut );
352         fprintf( stream, "  max age: %d\n",   ctl->maxQueryAge );
353         fprintf( stream, "match opt: %d\n",   ctl->matchingOption );
354         fprintf( stream, "  version: %d\n",   ctl->version );
355         fprintf( stream, " STARTTLS: %s\n",   ctl->enableTLS ? "yes" : "no" );
356         fprintf( stream, "  TLS: %s\n",   ctl->enableSSL ? "yes" : "no" );
357         fprintf( stream, "crit list:\n" );
358         if( ctl->listCriteria ) {
359                 mgu_print_dlist( ctl->listCriteria, stream );
360         }
361         else {
362                 fprintf( stream, "\t!!!none!!!\n" );
363         }
364         pthread_mutex_unlock( ctl->mutexCtl );
365 }
366 #endif
367
368 /**
369  * Copy member variables to specified object. Mutex lock object is
370  * not copied.
371  * \param ctlFrom Object to copy from.
372  * \param ctlTo   Destination object.
373  */
374 void ldapctl_copy( const LdapControl *ctlFrom, LdapControl *ctlTo ) {
375         GList *node;
376
377         cm_return_if_fail( ctlFrom != NULL );
378         cm_return_if_fail( ctlTo != NULL );
379
380         debug_print("ldap controller copy\n");
381         /* Lock both objects */
382         pthread_mutex_lock( ctlFrom->mutexCtl );
383         pthread_mutex_lock( ctlTo->mutexCtl );
384
385         /* Clear our destination */
386         ldapctl_clear( ctlTo );
387
388         /* Copy strings */
389         ctlTo->hostName = g_strdup( ctlFrom->hostName );
390         ctlTo->baseDN = g_strdup( ctlFrom->baseDN );
391         ctlTo->bindDN = g_strdup( ctlFrom->bindDN );
392         ctlTo->attribEMail = g_strdup( ctlFrom->attribEMail );
393         ctlTo->attribCName = g_strdup( ctlFrom->attribCName );
394         ctlTo->attribFName = g_strdup( ctlFrom->attribFName );
395         ctlTo->attribLName = g_strdup( ctlFrom->attribLName );
396         ctlTo->attribDName = g_strdup( ctlFrom->attribDName );
397
398         /* Copy search criteria */
399         node = ctlFrom->listCriteria;
400         while( node ) {
401                 ctlTo->listCriteria = g_list_append(
402                         ctlTo->listCriteria, g_strdup( node->data ) );
403                 node = g_list_next( node );
404         }
405
406         /* Copy other members */
407         ctlTo->port = ctlFrom->port;
408         ctlTo->maxEntries = ctlFrom->maxEntries;
409         ctlTo->timeOut = ctlFrom->timeOut;
410         ctlTo->maxQueryAge = ctlFrom->maxQueryAge;
411         ctlTo->matchingOption = ctlFrom->matchingOption;
412         ctlTo->version = ctlFrom->version;
413         ctlTo->enableTLS = ctlFrom->enableTLS;
414         ctlTo->enableSSL = ctlFrom->enableSSL;
415
416         /* Unlock */
417         pthread_mutex_unlock( ctlTo->mutexCtl );
418         pthread_mutex_unlock( ctlFrom->mutexCtl );
419 }
420
421 /**
422  * Search criteria fragment - two terms - begin with (default).
423  */
424 static gchar *_criteria2BeginWith = "(&(givenName=%s*)(sn=%s*))";
425
426 /**
427  * Search criteria fragment - two terms - contains.
428  */
429 static gchar *_criteria2Contains  = "(&(givenName=*%s*)(sn=*%s*))";
430
431 /**
432  * Create an LDAP search criteria by parsing specified search term. The search
433  * term may contain two names separated by the first embedded space found in
434  * the search term. It is assumed that the two tokens are first name and last
435  * name, or vice versa. An appropriate search criteria will be constructed.
436  *
437  * \param  searchTerm   Reference to search term to process.
438  * \param  matchOption  Set to the following:
439  * <ul>
440  * <li><code>LDAPCTL_MATCH_BEGINWITH</code> for "begins with" search</li>
441  * <li><code>LDAPCTL_MATCH_CONTAINS</code> for "contains" search</li>
442  * </ul>
443  *
444  * \return Formatted search criteria, or <code>NULL</code> if there is no
445  *         embedded spaces. The search term should be g_free() when no
446  *         longer required.
447  */
448 static gchar *ldapctl_build_ldap_criteria(
449                 const gchar *searchTerm, const gint matchOption )
450 {
451         gchar *p;
452         gchar *t1;
453         gchar *t2 = NULL;
454         gchar *term;
455         gchar *crit = NULL;
456         gchar *criteriaFmt;
457
458         if( matchOption == LDAPCTL_MATCH_CONTAINS ) {
459                 criteriaFmt = _criteria2Contains;
460         }
461         else {
462                 criteriaFmt = _criteria2BeginWith;
463         }
464
465         term = g_strdup( searchTerm );
466         g_strstrip( term );
467
468         /* Find first space character */        
469         t1 = p = term;
470         while( *p ) {
471                 if( *p == ' ' ) {
472                         *p = '\0';
473                         t2 = g_strdup( 1 + p );
474                         break;
475                 }
476                 p++;
477         }
478
479         if( t2 ) {
480                 /* Format search criteria */
481                 gchar *p1, *p2;
482
483                 g_strstrip( t2 );
484                 p1 = g_strdup_printf( criteriaFmt, t1, t2 );
485                 p2 = g_strdup_printf( criteriaFmt, t2, t1 );
486                 crit = g_strdup_printf( "(&(|%s%s)(mail=*))", p1, p2 );
487
488                 g_free( t2 );
489                 g_free( p1 );
490                 g_free( p2 );
491         }
492         g_free( term );
493         debug_print("search criteria: %s\n", crit?crit:"null");
494         return crit;
495 }
496
497
498 /**
499  * Search criteria fragment - single term - begin with (default).
500  */
501 static gchar *_criteriaBeginWith = "(%s=%s*)";
502
503 /**
504  * Search criteria fragment - single term - contains.
505  */
506 static gchar *_criteriaContains  = "(%s=*%s*)";
507
508 /**
509  * Build a formatted LDAP search criteria string from criteria list.
510  * \param ctl  Control object to process.
511  * \param searchVal Value to search for.
512  * \return Formatted string. Should be g_free() when done.
513  */
514 gchar *ldapctl_format_criteria( LdapControl *ctl, const gchar *searchVal ) {
515         GList *node;
516         gchar *p1, *p2, *retVal;
517         gchar *criteriaFmt;
518
519         cm_return_val_if_fail( ctl != NULL, NULL );
520         cm_return_val_if_fail( searchVal != NULL, NULL );
521
522         /* Test whether there are more that one search terms */
523         retVal = ldapctl_build_ldap_criteria( searchVal, ctl->matchingOption );
524         if( retVal ) return retVal;
525
526         if( ctl->matchingOption ==  LDAPCTL_MATCH_CONTAINS ) {
527                 criteriaFmt = _criteriaContains;
528         }
529         else {
530                 criteriaFmt = _criteriaBeginWith;
531         }
532
533         /* No - just a simple search */
534         /* p1 contains previous formatted criteria */
535         /* p2 contains next formatted criteria */
536         retVal = p1 = p2 = NULL;
537         node = ctl->listCriteria;
538         while( node ) {
539                 gchar *attr, *tmp;
540                 attr = node->data;
541                 node = g_list_next( node );
542
543                 /* Switch pointers */
544                 tmp = p1; p1 = p2; p2 = tmp;
545
546                 if( p1 ) {
547                         /* Subsequent time through */
548                         gchar *crit;
549
550                         debug_print("crit: %s\n", searchVal);
551                         /* fix bug when doing a search any */
552                         if (strcmp("*@", searchVal) == 0) {
553                             crit = g_strdup_printf( "(%s=*)", attr );
554                         }
555                         else {
556                             /* Format query criteria */
557                             crit = g_strdup_printf( criteriaFmt, attr, searchVal );
558                         }
559
560                         /* Append to existing criteria */                       
561                         g_free( p2 );
562                         p2 = g_strdup_printf( "(|%s%s)", p1, crit );
563
564                         g_free( crit );
565                 }
566                 else {
567                         /* First time through - Format query criteria */
568                         /* fix bug when doing a search any */
569                         if (strcmp("*@", searchVal) == 0) {
570                             p2 = g_strdup_printf( "(%s=*)", attr );
571                         }
572                         else {
573                             p2 = g_strdup_printf( criteriaFmt, attr, searchVal );
574                         }
575                 }
576         }
577
578         if( p2 == NULL ) {
579                 /* Nothing processed - format a default attribute */
580                 retVal = g_strdup_printf( "(%s=*)", LDAPCTL_ATTR_EMAIL );
581         }
582         else {
583                 /* We have something - free up previous result */
584                 retVal = p2;
585         }
586         if (p1)
587                 g_free( p1 );
588         debug_print("current search string: %s\n", retVal);
589         return retVal;
590 }
591
592 /**
593  * Return array of pointers to attributes for LDAP query.
594  * \param  ctl  Control object to process.
595  * \return NULL terminated list.
596  */
597 char **ldapctl_attribute_array( LdapControl *ctl ) {
598         char **ptrArray;
599         GList *node;
600         guint cnt, i;
601         cm_return_val_if_fail( ctl != NULL, NULL );
602
603         node = ctl->listCriteria;
604         cnt = g_list_length( ctl->listCriteria );
605         ptrArray = g_new0( char *, 1 + cnt );
606         i = 0;
607         while( node ) {
608                 ptrArray[ i++ ] = node->data;
609                 /*debug_print("adding search attribute: %s\n", (gchar *) node->data);*/
610                 node = g_list_next( node );
611         }
612         ptrArray[ i ] = NULL;
613         return ptrArray;
614 }
615
616 /**
617  * Return array of pointers to attributes for LDAP query.
618  * \param  ctl  Control object to process.
619  * \return NULL terminated list.
620  */
621 char **ldapctl_full_attribute_array( LdapControl *ctl ) {
622         char **ptrArray;
623         GList *node, *def;
624         GList *tmp = NULL;
625         guint cnt, i;
626         cm_return_val_if_fail( ctl != NULL, NULL );
627
628         def = ctl->listCriteria;
629         while (def) {
630                 tmp = g_list_append(tmp, g_strdup(def->data));
631                 def = def->next;
632         }
633
634         def = ldapctl_get_default_criteria_list();
635         node = def;
636         
637         while (node) {
638                 if( g_list_find_custom(tmp, (gpointer)def->data, 
639                                 (GCompareFunc)g_strcmp0) == NULL) {
640                         tmp = g_list_append(tmp, g_strdup(node->data));
641                 }
642                 node = node->next;
643         }
644
645         g_list_free_full(def, g_free);
646
647         node = tmp;
648         cnt = g_list_length( tmp );
649         ptrArray = g_new0( char *, 1 + cnt);
650         i = 0;
651         while( node ) {
652                 ptrArray[ i++ ] = node->data;
653                 /*debug_print("adding search attribute: %s\n", (gchar *) node->data);*/
654                 node = g_list_next( node );
655         }
656         g_list_free(tmp);
657         ptrArray[ i ] = NULL;
658         return ptrArray;
659 }
660
661 /**
662  * Free array of pointers allocated by ldapctl_criteria_array().
663  * param ptrArray Array to clear.
664  */
665 void ldapctl_free_attribute_array( char **ptrArray ) {
666         gint i;
667
668         /* Clear array to NULL's */
669         for( i = 0; ptrArray[i] != NULL; i++ ) {
670                 g_free(ptrArray[i]);
671                 ptrArray[i] = NULL;
672         }
673         g_free( ptrArray );
674 }       
675
676 /**
677  * Parse LDAP search string, building list of LDAP criteria attributes. This
678  * may be used to convert an old style Sylpheed LDAP search criteria to the
679  * new format. The old style uses a standard LDAP search string, for example:
680  * <pre>
681  *    (&(mail=*)(cn=%s*))
682  * </pre>
683  * This function extracts the two LDAP attributes <code>mail</code> and
684  * <code>cn</code>, adding each to a list.
685  *
686  * \param ctl Control object to process.
687  * \param criteria LDAP search criteria string.
688  */
689 void ldapctl_parse_ldap_search( LdapControl *ctl, gchar *criteria ) {
690         gchar *ptr;
691         gchar *pFrom;
692         gchar *attrib;
693         gint iLen;
694
695         cm_return_if_fail( ctl != NULL );
696
697         ldapctl_criteria_list_clear( ctl );
698         if( criteria == NULL ) return;
699
700         pFrom = NULL;
701         ptr = criteria;
702         while( *ptr ) {
703                 if( *ptr == '(' ) {
704                         pFrom = 1 + ptr;
705                 }
706                 if( *ptr == '=' ) {
707                         if( pFrom ) {
708                                 iLen = ptr - pFrom;
709                                 attrib = g_strndup( pFrom, iLen );
710                                 g_strstrip( attrib );
711                                 ldapctl_criteria_list_add( ctl, attrib );
712                                 g_free( attrib );
713                         }
714                         pFrom = NULL;
715                 }
716                 ptr++;
717         }
718 }
719
720 /**
721  * Return the default LDAP search criteria string.
722  * \return Formatted string or <i>""</i>. Should be g_free() when done.
723  */
724 gchar *ldapctl_get_default_criteria() {
725         gchar *retVal = g_strdup(LDAPCTL_DFL_ATTR_LIST);
726         const gchar **attrs = ATTRIBUTE; 
727
728         while (*attrs) {
729                 gchar *tmp = g_strdup_printf("%s, %s", retVal, *attrs++);
730                 g_free(retVal);
731                 retVal = tmp;
732         }
733         debug_print("default search criteria: %s\n", retVal);
734         return retVal;
735 }
736
737 /**
738  * Return the default LDAP search criteria list.
739  * \return GList or <i>NULL</i>.
740  */
741 GList *ldapctl_get_default_criteria_list() {
742         gchar *criteria, *item;
743         gchar **c_list, **w_list;
744         GList *attr_list = NULL;
745
746         criteria = ldapctl_get_default_criteria();
747         c_list = g_strsplit(criteria, " ", 0);
748         g_free(criteria);
749         criteria = NULL;
750         w_list = c_list;
751         while ((criteria = *w_list++) != 0) {
752                 /* copy string elimination <,> */
753                 if (*w_list)
754                         item = g_strndup(criteria, strlen(criteria) - 1);
755                 else
756                         item = g_strdup(criteria);
757                 debug_print("adding attribute to list: %s\n", item);
758                 attr_list = g_list_append(attr_list, item);
759         }
760         g_strfreev(c_list);
761         return attr_list;
762 }
763
764 /**
765  * Compare to GList for equality.
766  * \param l1 First GList
767  * \param l2 Second GList
768  * \Return TRUE or FALSE
769  */
770 gboolean ldapctl_compare_list(GList *l1, GList *l2) {
771         gchar *first, *second;
772         if (! l1 && ! l2)
773                 return TRUE;
774         if ((! l1 && l2) || (l1 && ! l2))
775                 return FALSE;
776         while (l1 && l2) {
777                 first = (gchar *) l1->data;
778                 second = (gchar *) l2->data;
779                 /*debug_print("comparing: %s = %s\n", first, second);*/
780                 if ( ! (first && second) || strcmp(first, second) != 0) {
781                         return FALSE;
782                 }
783                 l1 = g_list_next(l1);
784                 l2 = g_list_next(l2);
785         }
786         return TRUE;
787 }
788
789 #endif  /* USE_LDAP */
790
791 /*
792  * End of Source.
793  */
794