1 /*
2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 2003-2012 Match Grun and the Claws Mail team
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20 /*
21 * Export address book to LDIF file.
22 */
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #include "claws-features.h"
26 #endif
27
28 #include <sys/stat.h>
29 #include <dirent.h>
30 #include <errno.h>
31 #include <time.h>
32 #include <string.h>
33 #include <glib.h>
34 #include <glib/gi18n.h>
35
36 #include "mgutils.h"
37 #include "utils.h"
38 #include "exportldif.h"
39 #include "xmlprops.h"
40 #include "ldif.h"
41 #include "file-utils.h"
42
43
44 #ifdef MKDIR_TAKES_ONE_ARG
45 #undef mkdir
46 #define mkdir(a,b) mkdir(a)
47 #endif
48
49 #define DFL_DIR_CLAWS_OUT "claws-mail-out"
50 #define DFL_FILE_CLAWS_OUT "addressbook.ldif"
51
52 #define FMT_BUFSIZE 2048
53 #define XML_BUFSIZE 2048
54
55 /* Settings - properties */
56 #define EXML_PROPFILE_NAME "exportldif.xml"
57 #define EXMLPROP_DIRECTORY "directory"
58 #define EXMLPROP_FILE "file"
59 #define EXMLPROP_SUFFIX "suffix"
60 #define EXMLPROP_RDN_INDEX "rdn"
61 #define EXMLPROP_USE_DN "use-dn"
62 #define EXMLPROP_EXCL_EMAIL "exclude-mail"
63
64 static gchar *_attrName_UID_ = "uid";
65 static gchar *_attrName_DName_ = "cn";
66 static gchar *_attrName_EMail_ = "mail";
67
68 /**
69 * Create initialized LDIF export control object.
70 * \return Initialized export control data.
71 */
exportldif_create(void)72 ExportLdifCtl *exportldif_create( void ) {
73 ExportLdifCtl *ctl = g_new0( ExportLdifCtl, 1 );
74
75 ctl->path = NULL;
76 ctl->dirOutput = NULL;
77 ctl->fileLdif = NULL;
78 ctl->suffix = NULL;
79 ctl->rdnIndex = EXPORT_LDIF_ID_UID;
80 ctl->useDN = FALSE;
81 ctl->excludeEMail = TRUE;
82 ctl->retVal = MGU_SUCCESS;
83 ctl->rcCreate = 0;
84 ctl->settingsFile = g_strconcat(
85 get_rc_dir(), G_DIR_SEPARATOR_S, EXML_PROPFILE_NAME, NULL );
86
87 return ctl;
88 }
89
90 /**
91 * Free up object by releasing internal memory.
92 * \return ctl Export control data.
93 */
exportldif_free(ExportLdifCtl * ctl)94 void exportldif_free( ExportLdifCtl *ctl ) {
95 cm_return_if_fail( ctl != NULL );
96
97 g_free( ctl->path );
98 g_free( ctl->fileLdif );
99 g_free( ctl->dirOutput );
100 g_free( ctl->suffix );
101 g_free( ctl->settingsFile );
102
103 /* Clear pointers */
104 ctl->path = NULL;
105 ctl->dirOutput = NULL;
106 ctl->fileLdif = NULL;
107 ctl->suffix = NULL;
108 ctl->rdnIndex = EXPORT_LDIF_ID_UID;
109 ctl->useDN = FALSE;
110 ctl->excludeEMail = FALSE;
111 ctl->retVal = MGU_SUCCESS;
112 ctl->rcCreate = 0;
113
114 /* Now release object */
115 g_free( ctl );
116 }
117
118 /**
119 * Specify suffix to be used for creating DN entries.
120 * \param ctl Export control data.
121 * \param value Suffix.
122 */
exportldif_set_suffix(ExportLdifCtl * ctl,const char * value)123 void exportldif_set_suffix( ExportLdifCtl *ctl, const char *value ) {
124 cm_return_if_fail( ctl != NULL );
125 ctl->suffix = mgu_replace_string( ctl->suffix, value );
126 g_strstrip( ctl->suffix );
127 }
128
129 /**
130 * Specify index of variable to be used for creating RDN entries.
131 * \param ctl Export control data.
132 * \param value Index to variable, as follows:
133 * <ul>
134 * <li><code>EXPORT_LDIF_ID_UID</code> - Use Claws Mail UID.</li>
135 * <li><code>EXPORT_LDIF_ID_DNAME</code> - Use Claws Mail display name.</li>
136 * <li><code>EXPORT_LDIF_ID_EMAIL</code> - Use first Email address.</li>
137 * </ul>
138 */
exportldif_set_rdn(ExportLdifCtl * ctl,const gint value)139 void exportldif_set_rdn( ExportLdifCtl *ctl, const gint value ) {
140 cm_return_if_fail( ctl != NULL );
141 ctl->rdnIndex = value;
142 }
143
144 /**
145 * Specify that <code>DN</code> attribute, if present, should be used as the
146 * DN for the entry.
147 * \param ctl Export control data.
148 * \param value <i>TRUE</i> if DN should be used.
149 */
exportldif_set_use_dn(ExportLdifCtl * ctl,const gboolean value)150 void exportldif_set_use_dn( ExportLdifCtl *ctl, const gboolean value ) {
151 cm_return_if_fail( ctl != NULL );
152 ctl->useDN = value;
153 }
154
155 /**
156 * Specify that records without E-Mail addresses should be excluded.
157 * \param ctl Export control data.
158 * \param value <i>TRUE</i> if records without E-Mail should be excluded.
159 */
exportldif_set_exclude_email(ExportLdifCtl * ctl,const gboolean value)160 void exportldif_set_exclude_email( ExportLdifCtl *ctl, const gboolean value ) {
161 cm_return_if_fail( ctl != NULL );
162 ctl->excludeEMail = value;
163 }
164
165 /**
166 * Format LDAP value name with no embedded commas.
167 * \param value Data value to format.
168 * \return Formatted string, should be freed after use.
169 */
exportldif_fmt_value(gchar * value)170 static gchar *exportldif_fmt_value( gchar *value ) {
171 gchar *dupval;
172 gchar *src;
173 gchar *dest;
174 gchar ch;
175
176 /* Duplicate incoming value */
177 dest = dupval = g_strdup( value );
178
179 /* Copy characters, ignoring commas */
180 src = value;
181 while( *src ) {
182 ch = *src;
183 if( ch != ',' ) {
184 *dest = ch;
185 dest++;
186 }
187 src++;
188 }
189 *dest = '\0';
190 return dupval;
191 }
192
193 /**
194 * Build DN for entry.
195 * \param ctl Export control data.
196 * \param person Person to format.
197 * \return Formatted DN entry.
198 */
exportldif_fmt_dn(ExportLdifCtl * ctl,const ItemPerson * person)199 static gchar *exportldif_fmt_dn(
200 ExportLdifCtl *ctl, const ItemPerson *person )
201 {
202 gchar buf[ FMT_BUFSIZE + 1 ];
203 gchar *retVal = NULL;
204 gchar *attr = NULL;
205 gchar *value = NULL;
206 gchar *dupval = NULL;
207
208 /* Process RDN */
209 *buf = '\0';
210 if( ctl->rdnIndex == EXPORT_LDIF_ID_UID ) {
211 attr = _attrName_UID_;
212 value = ADDRITEM_ID( person );
213 }
214 else if( ctl->rdnIndex == EXPORT_LDIF_ID_DNAME ) {
215 attr = _attrName_DName_;
216 value = ADDRITEM_NAME( person );
217 dupval = exportldif_fmt_value( value );
218 }
219 else if( ctl->rdnIndex == EXPORT_LDIF_ID_EMAIL ) {
220 GList *node;
221
222 node = person->listEMail;
223 if( node ) {
224 ItemEMail *email = node->data;
225
226 attr = _attrName_EMail_;
227 value = email->address;
228 dupval = exportldif_fmt_value( value );
229 }
230 }
231
232 /* Format DN */
233 if( attr ) {
234 if( value ) {
235 if( strlen( value ) > 0 ) {
236 strncat( buf, attr, FMT_BUFSIZE - strlen(buf) );
237 strncat( buf, "=", FMT_BUFSIZE - strlen(buf) );
238 if( dupval ) {
239 /* Format and free duplicated value */
240 strncat( buf, dupval, FMT_BUFSIZE - strlen(buf) );
241 g_free( dupval );
242 }
243 else {
244 /* Use original value */
245 strncat( buf, value, FMT_BUFSIZE - strlen(buf) );
246 }
247
248 /* Append suffix */
249 if( ctl->suffix ) {
250 if( strlen( ctl->suffix ) > 0 ) {
251 strncat( buf, ",", FMT_BUFSIZE - strlen(buf) );
252 strncat( buf, ctl->suffix, FMT_BUFSIZE - strlen(buf) );
253 }
254 }
255
256 retVal = g_strdup( buf );
257 }
258 }
259 }
260 return retVal;
261 }
262
263 /**
264 * Find DN by searching attribute list.
265 * \param ctl Export control data.
266 * \param person Person to format.
267 * \return Formatted DN entry, should be freed after use.
268 */
exportldif_find_dn(ExportLdifCtl * ctl,const ItemPerson * person)269 static gchar *exportldif_find_dn(
270 ExportLdifCtl *ctl, const ItemPerson *person )
271 {
272 gchar *retVal = NULL;
273 const GList *node;
274
275 node = person->listAttrib;
276 while( node ) {
277 UserAttribute *attrib = node->data;
278
279 node = g_list_next( node );
280 if( g_utf8_collate( attrib->name, LDIF_TAG_DN ) == 0 ) {
281 retVal = g_strdup( attrib->value );
282 break;
283 }
284 }
285 return retVal;
286 }
287
288 /**
289 * Format E-Mail entries for person.
290 * \param person Person to format.
291 * \param stream Output stream.
292 * \return <i>TRUE</i> if entry formatted.
293 */
exportldif_fmt_email(const ItemPerson * person,FILE * stream)294 static gboolean exportldif_fmt_email( const ItemPerson *person, FILE *stream ) {
295 gboolean retVal = FALSE;
296 const GList *node;
297
298 node = person->listEMail;
299 while( node ) {
300 ItemEMail *email = node->data;
301
302 node = g_list_next( node );
303 ldif_write_value( stream, LDIF_TAG_EMAIL, email->address );
304 retVal = TRUE;
305 }
306 return retVal;
307 }
308
309 /**
310 * Test for E-Mail entries for person.
311 * \param person Person to test.
312 * \return <i>TRUE</i> if person has E-Mail address.
313 */
exportldif_test_email(const ItemPerson * person)314 static gboolean exportldif_test_email( const ItemPerson *person )
315 {
316 gboolean retVal = FALSE;
317 const GList *node;
318
319 node = person->listEMail;
320 while( node ) {
321 ItemEMail *email = node->data;
322
323 node = g_list_next( node );
324 if( email->address ) {
325 if( strlen( email->address ) > 0 ) {
326 retVal = TRUE;
327 break;
328 }
329 }
330 retVal = TRUE;
331 }
332 return retVal;
333 }
334
335 /**
336 * Format other attributes for person.
337 * \param person ItemPerson.
338 * \param stream Output stream.
339 */
exportldif_fmt_other_attributes(ItemPerson * person,FILE * stream)340 static void exportldif_fmt_other_attributes(ItemPerson* person, FILE* stream) {
341 UserAttribute* attr;
342 GList* attrList = NULL;
343 gchar* attrib;
344
345 if (! person)
346 return;
347 debug_print("cn: %s\n-----------------------------\n", ADDRITEM_NAME(person));
348 attrList = person->listAttrib;
349 while (attrList) {
350 attr = (UserAttribute *) attrList->data;
351 if (attr->uid) {
352 /* Native address book which does not conform to
353 * the LDAP schemas
354 */
355 attrib = g_strdup_printf("# %s", attr->name);
356 }
357 else {
358 attrib = g_strdup(attr->name);
359 }
360 debug_print("name: %s\nvalue: %s\n", attrib, attr->value);
361 ldif_write_value(stream, attrib, attr->value);
362 g_free(attrib);
363 attrList = g_list_next(attrList);
364 }
365 debug_print("-------------------------------\n");
366 }
367
368 /**
369 * Find persons displayName.
370 * \param person ItemPerson.
371 * \return displayName.
372 */
exportldif_find_displayName(ItemPerson * person)373 static gchar* exportldif_find_displayName(ItemPerson* person) {
374 gchar* displayName;
375
376 if (! person)
377 return NULL;
378
379 if (person->nickName && strlen(person->nickName) > 0)
380 displayName = g_strdup(person->nickName);
381 else
382 displayName = g_strdup(ADDRITEM_NAME(person));
383 return displayName;
384 }
385
386 /**
387 * Format persons in an address book folder.
388 * \param ctl Export control data.
389 * \param stream Output stream.
390 * \param folder Folder to format.
391 * \return <i>TRUE</i> if no persons were formatted.
392 */
exportldif_fmt_person(ExportLdifCtl * ctl,FILE * stream,const ItemFolder * folder)393 static gboolean exportldif_fmt_person(
394 ExportLdifCtl *ctl, FILE *stream, const ItemFolder *folder )
395 {
396 gboolean retVal = TRUE;
397 const GList *node;
398 gchar* sn = NULL;
399 gchar* displayName = NULL;
400
401 if( folder->listPerson == NULL ) return retVal;
402
403 node = folder->listPerson;
404 while( node ) {
405 AddrItemObject *aio = node->data;
406 node = g_list_next( node );
407
408 if( aio && aio->type == ITEMTYPE_PERSON ) {
409 ItemPerson *person = ( ItemPerson * ) aio;
410 gboolean classPerson = FALSE;
411 gboolean classInetP = FALSE;
412 gchar *dn = NULL;
413
414 /* Check for E-Mail */
415 if( exportldif_test_email( person ) ) {
416 classInetP = TRUE;
417 }
418 else {
419 /* Bail if no E-Mail address */
420 if( ctl->excludeEMail ) continue;
421 }
422
423 /* Format DN */
424 if( ctl->useDN ) {
425 dn = exportldif_find_dn( ctl, person );
426 }
427 if( dn == NULL ) {
428 dn = exportldif_fmt_dn( ctl, person );
429 }
430 if( dn == NULL ) continue;
431 ldif_write_value( stream, LDIF_TAG_DN, dn );
432 g_free( dn );
433
434 /*
435 * Test for schema requirements. This is a simple
436 * test and does not trap all LDAP schema errors.
437 * These can be detected when the LDIF file is
438 * loaded into an LDAP server.
439 */
440 if( person->lastName ) {
441 if( strlen( person->lastName ) > 0 ) {
442 classPerson = TRUE;
443 classInetP = TRUE;
444 }
445 }
446
447 if( classPerson ) {
448 ldif_write_value( stream,
449 LDIF_TAG_OBJECTCLASS, LDIF_CLASS_PERSON );
450 }
451 if( classInetP ) {
452 ldif_write_value( stream,
453 LDIF_TAG_OBJECTCLASS, LDIF_CLASS_INET_PERSON );
454 }
455
456 /* Format person attributes */
457 ldif_write_value(
458 stream, LDIF_TAG_COMMONNAME, ADDRITEM_NAME( person ) );
459 sn = g_strdup(person->lastName);
460 if (classPerson || classInetP) {
461 if(! sn || strcmp("", sn) == 0 || strcmp(" ", sn) == 0) {
462 g_free(sn);
463 sn = g_strdup("Some SN");
464 }
465 }
466 ldif_write_value(
467 stream, LDIF_TAG_LASTNAME, sn );
468 g_free(sn);
469 sn = NULL;
470 ldif_write_value(
471 stream, LDIF_TAG_FIRSTNAME, person->firstName );
472
473 if (! person->externalID)
474 displayName = exportldif_find_displayName(person);
475 else
476 displayName = g_strdup(person->nickName);
477 ldif_write_value(stream, LDIF_TAG_NICKNAME, displayName);
478 g_free(displayName);
479 displayName = NULL;
480
481 /* Format E-Mail */
482 exportldif_fmt_email( person, stream );
483
484 /* Handle other attributes */
485 exportldif_fmt_other_attributes(person, stream);
486
487 /* End record */
488 ldif_write_eor( stream );
489
490 retVal = FALSE;
491 }
492 }
493
494 return retVal;
495 }
496
497 /**
498 * Format an address book folder.
499 * \param ctl Export control data.
500 * \param stream Output stream.
501 * \param folder Folder to format.
502 * \return <i>TRUE</i> if no persons were formatted.
503 */
exportldif_fmt_folder(ExportLdifCtl * ctl,FILE * stream,const ItemFolder * folder)504 static void exportldif_fmt_folder(
505 ExportLdifCtl *ctl, FILE *stream, const ItemFolder *folder )
506 {
507 const GList *node;
508
509 /* Export entries in this folder */
510 exportldif_fmt_person( ctl, stream, folder );
511
512 /* Export entries in sub-folders */
513 node = folder->listFolder;
514 while( node ) {
515 AddrItemObject *aio = node->data;
516
517 node = g_list_next( node );
518 if( aio && aio->type == ITEMTYPE_FOLDER ) {
519 ItemFolder *subFolder = ( ItemFolder * ) aio;
520 exportldif_fmt_folder( ctl, stream, subFolder );
521 }
522 }
523 }
524
525 /**
526 * Export address book to LDIF file.
527 * \param ctl Export control data.
528 * \param cache Address book/data source cache.
529 * \return Status.
530 */
exportldif_process(ExportLdifCtl * ctl,AddressCache * cache)531 void exportldif_process( ExportLdifCtl *ctl, AddressCache *cache )
532 {
533 ItemFolder *rootFolder;
534 FILE *ldifFile;
535
536 ldifFile = claws_fopen( ctl->path, "wb" );
537 if( ! ldifFile ) {
538 /* Cannot open file */
539 ctl->retVal = MGU_OPEN_FILE;
540 return;
541 }
542
543 rootFolder = cache->rootFolder;
544 exportldif_fmt_folder( ctl, ldifFile, rootFolder );
545 claws_safe_fclose( ldifFile );
546 ctl->retVal = MGU_SUCCESS;
547 }
548
549 /**
550 * Build full export file specification.
551 * \param ctl Export control data.
552 */
exportldif_build_filespec(ExportLdifCtl * ctl)553 static void exportldif_build_filespec( ExportLdifCtl *ctl ) {
554 gchar *fileSpec;
555
556 fileSpec = g_strconcat(
557 ctl->dirOutput, G_DIR_SEPARATOR_S, ctl->fileLdif, NULL );
558 ctl->path = mgu_replace_string( ctl->path, fileSpec );
559 g_free( fileSpec );
560 }
561
562 /**
563 * Parse directory and filename from full export file specification.
564 * \param ctl Export control data.
565 * \param fileSpec File spec.
566 */
exportldif_parse_filespec(ExportLdifCtl * ctl,gchar * fileSpec)567 void exportldif_parse_filespec( ExportLdifCtl *ctl, gchar *fileSpec ) {
568 gchar *t;
569 gchar *base = g_path_get_basename(fileSpec);
570
571 ctl->fileLdif =
572 mgu_replace_string( ctl->fileLdif, base );
573 g_free(base);
574 t = g_path_get_dirname( fileSpec );
575 ctl->dirOutput = mgu_replace_string( ctl->dirOutput, t );
576 g_free( t );
577 ctl->path = mgu_replace_string( ctl->path, fileSpec );
578 }
579
580 /**
581 * Create output directory.
582 * \param ctl Export control data.
583 * \return TRUE if directory created.
584 */
exportldif_create_dir(ExportLdifCtl * ctl)585 gboolean exportldif_create_dir( ExportLdifCtl *ctl ) {
586 gboolean retVal = FALSE;
587
588 ctl->rcCreate = 0;
589 if( mkdir( ctl->dirOutput, S_IRWXU ) == 0 ) {
590 retVal = TRUE;
591 }
592 else {
593 ctl->rcCreate = errno;
594 }
595 return retVal;
596 }
597
598 /**
599 * Retrieve create directory error message.
600 * \param ctl Export control data.
601 * \return Message.
602 */
exportldif_get_create_msg(ExportLdifCtl * ctl)603 gchar *exportldif_get_create_msg( ExportLdifCtl *ctl ) {
604 gchar *msg;
605
606 if( ctl->rcCreate == EEXIST ) {
607 msg = _( "Name already exists but is not a directory." );
608 }
609 else if( ctl->rcCreate == EACCES ) {
610 msg = _( "No permissions to create directory." );
611 }
612 else if( ctl->rcCreate == ENAMETOOLONG ) {
613 msg = _( "Name is too long." );
614 }
615 else {
616 msg = _( "Not specified." );
617 }
618 return msg;
619 }
620
621 /**
622 * Set default values.
623 * \param ctl Export control data.
624 */
exportldif_default_values(ExportLdifCtl * ctl)625 static void exportldif_default_values( ExportLdifCtl *ctl ) {
626 gchar *str;
627
628 str = g_strconcat(
629 get_home_dir(), G_DIR_SEPARATOR_S,
630 DFL_DIR_CLAWS_OUT, NULL );
631
632 ctl->dirOutput = mgu_replace_string( ctl->dirOutput, str );
633 g_free( str );
634
635 ctl->fileLdif =
636 mgu_replace_string( ctl->fileLdif, DFL_FILE_CLAWS_OUT );
637 ctl->suffix = mgu_replace_string( ctl->suffix, "" );
638
639 ctl->rdnIndex = EXPORT_LDIF_ID_UID;
640 ctl->useDN = FALSE;
641 ctl->retVal = MGU_SUCCESS;
642 }
643
644 /**
645 * Load settings from XML properties file.
646 * \param ctl Export control data.
647 */
exportldif_load_settings(ExportLdifCtl * ctl)648 void exportldif_load_settings( ExportLdifCtl *ctl ) {
649 XmlProperty *props;
650 gint rc;
651 gchar buf[ XML_BUFSIZE ];
652
653 props = xmlprops_create();
654 xmlprops_set_path( props, ctl->settingsFile );
655 rc = xmlprops_load_file( props );
656 if( rc == 0 ) {
657 /* Read settings */
658 *buf = '\0';
659 xmlprops_get_property_s( props, EXMLPROP_DIRECTORY, buf );
660 ctl->dirOutput = mgu_replace_string( ctl->dirOutput, buf );
661
662 *buf = '\0';
663 xmlprops_get_property_s( props, EXMLPROP_FILE, buf );
664 ctl->fileLdif = mgu_replace_string( ctl->fileLdif, buf );
665
666 *buf = '\0';
667 xmlprops_get_property_s( props, EXMLPROP_SUFFIX, buf );
668 ctl->suffix = mgu_replace_string( ctl->suffix, buf );
669
670 ctl->rdnIndex =
671 xmlprops_get_property_i( props, EXMLPROP_RDN_INDEX );
672 ctl->useDN =
673 xmlprops_get_property_b( props, EXMLPROP_USE_DN );
674 ctl->excludeEMail =
675 xmlprops_get_property_b( props, EXMLPROP_EXCL_EMAIL );
676 }
677 else {
678 /* Set default values */
679 exportldif_default_values( ctl );
680 }
681 exportldif_build_filespec( ctl );
682 /* exportldif_print( ctl, stdout ); */
683
684 xmlprops_free( props );
685 }
686
687 /**
688 * Save settings to XML properties file.
689 * \param ctl Export control data.
690 */
exportldif_save_settings(ExportLdifCtl * ctl)691 void exportldif_save_settings( ExportLdifCtl *ctl ) {
692 XmlProperty *props;
693
694 props = xmlprops_create();
695 xmlprops_set_path( props, ctl->settingsFile );
696
697 xmlprops_set_property( props, EXMLPROP_DIRECTORY, ctl->dirOutput );
698 xmlprops_set_property( props, EXMLPROP_FILE, ctl->fileLdif );
699 xmlprops_set_property( props, EXMLPROP_SUFFIX, ctl->suffix );
700 xmlprops_set_property_i( props, EXMLPROP_RDN_INDEX, ctl->rdnIndex );
701 xmlprops_set_property_b( props, EXMLPROP_USE_DN, ctl->useDN );
702 xmlprops_set_property_b( props, EXMLPROP_EXCL_EMAIL, ctl->excludeEMail );
703 if (xmlprops_save_file( props ) != MGU_SUCCESS)
704 g_warning("can't save settings");
705 xmlprops_free( props );
706 }
707
708 /*
709 * ============================================================================
710 * End of Source.
711 * ============================================================================
712 */
713
714
715