Fix bug #3574: Template addressing
[claws.git] / src / exportldif.c
blobc864975887ce2d714dc9b0064ef8be436aa8fa6f
1 /*
2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 2003-2012 Match Grun and the Claws Mail team
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.
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.
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/>.
21 * Export address book to LDIF file.
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #include "claws-features.h"
26 #endif
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>
36 #include "mgutils.h"
37 #include "utils.h"
38 #include "exportldif.h"
39 #include "xmlprops.h"
40 #include "ldif.h"
43 #ifdef MKDIR_TAKES_ONE_ARG
44 #undef mkdir
45 #define mkdir(a,b) mkdir(a)
46 #endif
48 #define DFL_DIR_CLAWS_OUT "claws-mail-out"
49 #define DFL_FILE_CLAWS_OUT "addressbook.ldif"
51 #define FMT_BUFSIZE 2048
52 #define XML_BUFSIZE 2048
54 /* Settings - properties */
55 #define EXML_PROPFILE_NAME "exportldif.xml"
56 #define EXMLPROP_DIRECTORY "directory"
57 #define EXMLPROP_FILE "file"
58 #define EXMLPROP_SUFFIX "suffix"
59 #define EXMLPROP_RDN_INDEX "rdn"
60 #define EXMLPROP_USE_DN "use-dn"
61 #define EXMLPROP_EXCL_EMAIL "exclude-mail"
63 static gchar *_attrName_UID_ = "uid";
64 static gchar *_attrName_DName_ = "cn";
65 static gchar *_attrName_EMail_ = "mail";
67 /**
68 * Create initialized LDIF export control object.
69 * \return Initialized export control data.
71 ExportLdifCtl *exportldif_create( void ) {
72 ExportLdifCtl *ctl = g_new0( ExportLdifCtl, 1 );
74 ctl->path = NULL;
75 ctl->dirOutput = NULL;
76 ctl->fileLdif = NULL;
77 ctl->suffix = NULL;
78 ctl->rdnIndex = EXPORT_LDIF_ID_UID;
79 ctl->useDN = FALSE;
80 ctl->excludeEMail = TRUE;
81 ctl->retVal = MGU_SUCCESS;
82 ctl->rcCreate = 0;
83 ctl->settingsFile = g_strconcat(
84 get_rc_dir(), G_DIR_SEPARATOR_S, EXML_PROPFILE_NAME, NULL );
86 return ctl;
89 /**
90 * Free up object by releasing internal memory.
91 * \return ctl Export control data.
93 void exportldif_free( ExportLdifCtl *ctl ) {
94 cm_return_if_fail( ctl != NULL );
96 g_free( ctl->path );
97 g_free( ctl->fileLdif );
98 g_free( ctl->dirOutput );
99 g_free( ctl->suffix );
100 g_free( ctl->settingsFile );
102 /* Clear pointers */
103 ctl->path = NULL;
104 ctl->dirOutput = NULL;
105 ctl->fileLdif = NULL;
106 ctl->suffix = NULL;
107 ctl->rdnIndex = EXPORT_LDIF_ID_UID;
108 ctl->useDN = FALSE;
109 ctl->excludeEMail = FALSE;
110 ctl->retVal = MGU_SUCCESS;
111 ctl->rcCreate = 0;
113 /* Now release object */
114 g_free( ctl );
118 * Specify suffix to be used for creating DN entries.
119 * \param ctl Export control data.
120 * \param value Suffix.
122 void exportldif_set_suffix( ExportLdifCtl *ctl, const char *value ) {
123 cm_return_if_fail( ctl != NULL );
124 ctl->suffix = mgu_replace_string( ctl->suffix, value );
125 g_strstrip( ctl->suffix );
129 * Specify index of variable to be used for creating RDN entries.
130 * \param ctl Export control data.
131 * \param value Index to variable, as follows:
132 * <ul>
133 * <li><code>EXPORT_LDIF_ID_UID</code> - Use Sylpheed UID.</li>
134 * <li><code>EXPORT_LDIF_ID_DNAME</code> - Use Sylpheed display name.</li>
135 * <li><code>EXPORT_LDIF_ID_EMAIL</code> - Use first Email address.</li>
136 * </ul>
138 void exportldif_set_rdn( ExportLdifCtl *ctl, const gint value ) {
139 cm_return_if_fail( ctl != NULL );
140 ctl->rdnIndex = value;
144 * Specify that <code>DN</code> attribute, if present, should be used as the
145 * DN for the entry.
146 * \param ctl Export control data.
147 * \param value <i>TRUE</i> if DN should be used.
149 void exportldif_set_use_dn( ExportLdifCtl *ctl, const gboolean value ) {
150 cm_return_if_fail( ctl != NULL );
151 ctl->useDN = value;
155 * Specify that records without E-Mail addresses should be excluded.
156 * \param ctl Export control data.
157 * \param value <i>TRUE</i> if records without E-Mail should be excluded.
159 void exportldif_set_exclude_email( ExportLdifCtl *ctl, const gboolean value ) {
160 cm_return_if_fail( ctl != NULL );
161 ctl->excludeEMail = value;
165 * Format LDAP value name with no embedded commas.
166 * \param value Data value to format.
167 * \return Formatted string, should be freed after use.
169 static gchar *exportldif_fmt_value( gchar *value ) {
170 gchar *dupval;
171 gchar *src;
172 gchar *dest;
173 gchar ch;
175 /* Duplicate incoming value */
176 dest = dupval = g_strdup( value );
178 /* Copy characters, ignoring commas */
179 src = value;
180 while( *src ) {
181 ch = *src;
182 if( ch != ',' ) {
183 *dest = ch;
184 dest++;
186 src++;
188 *dest = '\0';
189 return dupval;
193 * Build DN for entry.
194 * \param ctl Export control data.
195 * \param person Person to format.
196 * \return Formatted DN entry.
198 static gchar *exportldif_fmt_dn(
199 ExportLdifCtl *ctl, const ItemPerson *person )
201 gchar buf[ FMT_BUFSIZE + 1 ];
202 gchar *retVal = NULL;
203 gchar *attr = NULL;
204 gchar *value = NULL;
205 gchar *dupval = NULL;
207 /* Process RDN */
208 *buf = '\0';
209 if( ctl->rdnIndex == EXPORT_LDIF_ID_UID ) {
210 attr = _attrName_UID_;
211 value = ADDRITEM_ID( person );
213 else if( ctl->rdnIndex == EXPORT_LDIF_ID_DNAME ) {
214 attr = _attrName_DName_;
215 value = ADDRITEM_NAME( person );
216 dupval = exportldif_fmt_value( value );
218 else if( ctl->rdnIndex == EXPORT_LDIF_ID_EMAIL ) {
219 GList *node;
221 node = person->listEMail;
222 if( node ) {
223 ItemEMail *email = node->data;
225 attr = _attrName_EMail_;
226 value = email->address;
227 dupval = exportldif_fmt_value( value );
231 /* Format DN */
232 if( attr ) {
233 if( value ) {
234 if( strlen( value ) > 0 ) {
235 strncat( buf, attr, FMT_BUFSIZE - strlen(buf) );
236 strncat( buf, "=", FMT_BUFSIZE - strlen(buf) );
237 if( dupval ) {
238 /* Format and free duplicated value */
239 strncat( buf, dupval, FMT_BUFSIZE - strlen(buf) );
240 g_free( dupval );
242 else {
243 /* Use original value */
244 strncat( buf, value, FMT_BUFSIZE - strlen(buf) );
247 /* Append suffix */
248 if( ctl->suffix ) {
249 if( strlen( ctl->suffix ) > 0 ) {
250 strncat( buf, ",", FMT_BUFSIZE - strlen(buf) );
251 strncat( buf, ctl->suffix, FMT_BUFSIZE - strlen(buf) );
255 retVal = g_strdup( buf );
259 return retVal;
263 * Find DN by searching attribute list.
264 * \param ctl Export control data.
265 * \param person Person to format.
266 * \return Formatted DN entry, should be freed after use.
268 static gchar *exportldif_find_dn(
269 ExportLdifCtl *ctl, const ItemPerson *person )
271 gchar *retVal = NULL;
272 const GList *node;
274 node = person->listAttrib;
275 while( node ) {
276 UserAttribute *attrib = node->data;
278 node = g_list_next( node );
279 if( g_utf8_collate( attrib->name, LDIF_TAG_DN ) == 0 ) {
280 retVal = g_strdup( attrib->value );
281 break;
284 return retVal;
288 * Format E-Mail entries for person.
289 * \param person Person to format.
290 * \param stream Output stream.
291 * \return <i>TRUE</i> if entry formatted.
293 static gboolean exportldif_fmt_email( const ItemPerson *person, FILE *stream ) {
294 gboolean retVal = FALSE;
295 const GList *node;
297 node = person->listEMail;
298 while( node ) {
299 ItemEMail *email = node->data;
301 node = g_list_next( node );
302 ldif_write_value( stream, LDIF_TAG_EMAIL, email->address );
303 retVal = TRUE;
305 return retVal;
309 * Test for E-Mail entries for person.
310 * \param person Person to test.
311 * \return <i>TRUE</i> if person has E-Mail address.
313 static gboolean exportldif_test_email( const ItemPerson *person )
315 gboolean retVal = FALSE;
316 const GList *node;
318 node = person->listEMail;
319 while( node ) {
320 ItemEMail *email = node->data;
322 node = g_list_next( node );
323 if( email->address ) {
324 if( strlen( email->address ) > 0 ) {
325 retVal = TRUE;
326 break;
329 retVal = TRUE;
331 return retVal;
335 * Format other attributes for person.
336 * \param person ItemPerson.
337 * \param stream Output stream.
339 static void exportldif_fmt_other_attributes(ItemPerson* person, FILE* stream) {
340 UserAttribute* attr;
341 GList* attrList = NULL;
342 gchar* attrib;
344 if (! person)
345 return;
346 debug_print("cn: %s\n-----------------------------\n", ADDRITEM_NAME(person));
347 attrList = person->listAttrib;
348 while (attrList) {
349 attr = (UserAttribute *) attrList->data;
350 if (attr->uid) {
351 /* Native address book which does not conform to
352 * the LDAP schemas
354 attrib = g_strdup_printf("# %s", attr->name);
356 else {
357 attrib = g_strdup(attr->name);
359 debug_print("name: %s\nvalue: %s\n", attrib, attr->value);
360 ldif_write_value(stream, attrib, attr->value);
361 g_free(attrib);
362 attrList = g_list_next(attrList);
364 debug_print("-------------------------------\n");
368 * Find persons displayName.
369 * \param person ItemPerson.
370 * \return displayName.
372 static gchar* exportldif_find_displayName(ItemPerson* person) {
373 gchar* displayName;
375 if (! person)
376 return NULL;
378 if (person->nickName && strlen(person->nickName) > 0)
379 displayName = g_strdup(person->nickName);
380 else
381 displayName = g_strdup(ADDRITEM_NAME(person));
382 return displayName;
386 * Format persons in an address book folder.
387 * \param ctl Export control data.
388 * \param stream Output stream.
389 * \param folder Folder to format.
390 * \return <i>TRUE</i> if no persons were formatted.
392 static gboolean exportldif_fmt_person(
393 ExportLdifCtl *ctl, FILE *stream, const ItemFolder *folder )
395 gboolean retVal = TRUE;
396 const GList *node;
397 gchar* sn = NULL;
398 gchar* displayName = NULL;
400 if( folder->listPerson == NULL ) return retVal;
402 node = folder->listPerson;
403 while( node ) {
404 AddrItemObject *aio = node->data;
405 node = g_list_next( node );
407 if( aio && aio->type == ITEMTYPE_PERSON ) {
408 ItemPerson *person = ( ItemPerson * ) aio;
409 gboolean classPerson = FALSE;
410 gboolean classInetP = FALSE;
411 gchar *dn = NULL;
413 /* Check for E-Mail */
414 if( exportldif_test_email( person ) ) {
415 classInetP = TRUE;
417 else {
418 /* Bail if no E-Mail address */
419 if( ctl->excludeEMail ) continue;
422 /* Format DN */
423 if( ctl->useDN ) {
424 dn = exportldif_find_dn( ctl, person );
426 if( dn == NULL ) {
427 dn = exportldif_fmt_dn( ctl, person );
429 if( dn == NULL ) continue;
430 ldif_write_value( stream, LDIF_TAG_DN, dn );
431 g_free( dn );
434 * Test for schema requirements. This is a simple
435 * test and does not trap all LDAP schema errors.
436 * These can be detected when the LDIF file is
437 * loaded into an LDAP server.
439 if( person->lastName ) {
440 if( strlen( person->lastName ) > 0 ) {
441 classPerson = TRUE;
442 classInetP = TRUE;
446 if( classPerson ) {
447 ldif_write_value( stream,
448 LDIF_TAG_OBJECTCLASS, LDIF_CLASS_PERSON );
450 if( classInetP ) {
451 ldif_write_value( stream,
452 LDIF_TAG_OBJECTCLASS, LDIF_CLASS_INET_PERSON );
455 /* Format person attributes */
456 ldif_write_value(
457 stream, LDIF_TAG_COMMONNAME, ADDRITEM_NAME( person ) );
458 sn = g_strdup(person->lastName);
459 if (classPerson || classInetP) {
460 if(! sn || strcmp("", sn) == 0 || strcmp(" ", sn) == 0) {
461 g_free(sn);
462 sn = g_strdup("Some SN");
465 ldif_write_value(
466 stream, LDIF_TAG_LASTNAME, sn );
467 g_free(sn);
468 sn = NULL;
469 ldif_write_value(
470 stream, LDIF_TAG_FIRSTNAME, person->firstName );
472 if (! person->externalID)
473 displayName = exportldif_find_displayName(person);
474 else
475 displayName = g_strdup(person->nickName);
476 ldif_write_value(stream, LDIF_TAG_NICKNAME, displayName);
477 g_free(displayName);
478 displayName = NULL;
480 /* Format E-Mail */
481 exportldif_fmt_email( person, stream );
483 /* Handle other attributes */
484 exportldif_fmt_other_attributes(person, stream);
486 /* End record */
487 ldif_write_eor( stream );
489 retVal = FALSE;
493 return retVal;
497 * Format an address book folder.
498 * \param ctl Export control data.
499 * \param stream Output stream.
500 * \param folder Folder to format.
501 * \return <i>TRUE</i> if no persons were formatted.
503 static void exportldif_fmt_folder(
504 ExportLdifCtl *ctl, FILE *stream, const ItemFolder *folder )
506 const GList *node;
508 /* Export entries in this folder */
509 exportldif_fmt_person( ctl, stream, folder );
511 /* Export entries in sub-folders */
512 node = folder->listFolder;
513 while( node ) {
514 AddrItemObject *aio = node->data;
516 node = g_list_next( node );
517 if( aio && aio->type == ITEMTYPE_FOLDER ) {
518 ItemFolder *subFolder = ( ItemFolder * ) aio;
519 exportldif_fmt_folder( ctl, stream, subFolder );
525 * Export address book to LDIF file.
526 * \param ctl Export control data.
527 * \param cache Address book/data source cache.
528 * \return Status.
530 void exportldif_process( ExportLdifCtl *ctl, AddressCache *cache )
532 ItemFolder *rootFolder;
533 FILE *ldifFile;
535 ldifFile = g_fopen( ctl->path, "wb" );
536 if( ! ldifFile ) {
537 /* Cannot open file */
538 ctl->retVal = MGU_OPEN_FILE;
539 return;
542 rootFolder = cache->rootFolder;
543 exportldif_fmt_folder( ctl, ldifFile, rootFolder );
544 fclose( ldifFile );
545 ctl->retVal = MGU_SUCCESS;
549 * Build full export file specification.
550 * \param ctl Export control data.
552 static void exportldif_build_filespec( ExportLdifCtl *ctl ) {
553 gchar *fileSpec;
555 fileSpec = g_strconcat(
556 ctl->dirOutput, G_DIR_SEPARATOR_S, ctl->fileLdif, NULL );
557 ctl->path = mgu_replace_string( ctl->path, fileSpec );
558 g_free( fileSpec );
562 * Parse directory and filename from full export file specification.
563 * \param ctl Export control data.
564 * \param fileSpec File spec.
566 void exportldif_parse_filespec( ExportLdifCtl *ctl, gchar *fileSpec ) {
567 gchar *t;
568 gchar *base = g_path_get_basename(fileSpec);
570 ctl->fileLdif =
571 mgu_replace_string( ctl->fileLdif, base );
572 g_free(base);
573 t = g_path_get_dirname( fileSpec );
574 ctl->dirOutput = mgu_replace_string( ctl->dirOutput, t );
575 g_free( t );
576 ctl->path = mgu_replace_string( ctl->path, fileSpec );
580 * Create output directory.
581 * \param ctl Export control data.
582 * \return TRUE if directory created.
584 gboolean exportldif_create_dir( ExportLdifCtl *ctl ) {
585 gboolean retVal = FALSE;
587 ctl->rcCreate = 0;
588 if( mkdir( ctl->dirOutput, S_IRWXU ) == 0 ) {
589 retVal = TRUE;
591 else {
592 ctl->rcCreate = errno;
594 return retVal;
598 * Retrieve create directory error message.
599 * \param ctl Export control data.
600 * \return Message.
602 gchar *exportldif_get_create_msg( ExportLdifCtl *ctl ) {
603 gchar *msg;
605 if( ctl->rcCreate == EEXIST ) {
606 msg = _( "Name already exists but is not a directory." );
608 else if( ctl->rcCreate == EACCES ) {
609 msg = _( "No permissions to create directory." );
611 else if( ctl->rcCreate == ENAMETOOLONG ) {
612 msg = _( "Name is too long." );
614 else {
615 msg = _( "Not specified." );
617 return msg;
621 * Set default values.
622 * \param ctl Export control data.
624 static void exportldif_default_values( ExportLdifCtl *ctl ) {
625 gchar *str;
627 str = g_strconcat(
628 get_home_dir(), G_DIR_SEPARATOR_S,
629 DFL_DIR_CLAWS_OUT, NULL );
631 ctl->dirOutput = mgu_replace_string( ctl->dirOutput, str );
632 g_free( str );
634 ctl->fileLdif =
635 mgu_replace_string( ctl->fileLdif, DFL_FILE_CLAWS_OUT );
636 ctl->suffix = mgu_replace_string( ctl->suffix, "" );
638 ctl->rdnIndex = EXPORT_LDIF_ID_UID;
639 ctl->useDN = FALSE;
640 ctl->retVal = MGU_SUCCESS;
644 * Load settings from XML properties file.
645 * \param ctl Export control data.
647 void exportldif_load_settings( ExportLdifCtl *ctl ) {
648 XmlProperty *props;
649 gint rc;
650 gchar buf[ XML_BUFSIZE ];
652 props = xmlprops_create();
653 xmlprops_set_path( props, ctl->settingsFile );
654 rc = xmlprops_load_file( props );
655 if( rc == 0 ) {
656 /* Read settings */
657 *buf = '\0';
658 xmlprops_get_property_s( props, EXMLPROP_DIRECTORY, buf );
659 ctl->dirOutput = mgu_replace_string( ctl->dirOutput, buf );
661 *buf = '\0';
662 xmlprops_get_property_s( props, EXMLPROP_FILE, buf );
663 ctl->fileLdif = mgu_replace_string( ctl->fileLdif, buf );
665 *buf = '\0';
666 xmlprops_get_property_s( props, EXMLPROP_SUFFIX, buf );
667 ctl->suffix = mgu_replace_string( ctl->suffix, buf );
669 ctl->rdnIndex =
670 xmlprops_get_property_i( props, EXMLPROP_RDN_INDEX );
671 ctl->useDN =
672 xmlprops_get_property_b( props, EXMLPROP_USE_DN );
673 ctl->excludeEMail =
674 xmlprops_get_property_b( props, EXMLPROP_EXCL_EMAIL );
676 else {
677 /* Set default values */
678 exportldif_default_values( ctl );
680 exportldif_build_filespec( ctl );
681 /* exportldif_print( ctl, stdout ); */
683 xmlprops_free( props );
687 * Save settings to XML properties file.
688 * \param ctl Export control data.
690 void exportldif_save_settings( ExportLdifCtl *ctl ) {
691 XmlProperty *props;
693 props = xmlprops_create();
694 xmlprops_set_path( props, ctl->settingsFile );
696 xmlprops_set_property( props, EXMLPROP_DIRECTORY, ctl->dirOutput );
697 xmlprops_set_property( props, EXMLPROP_FILE, ctl->fileLdif );
698 xmlprops_set_property( props, EXMLPROP_SUFFIX, ctl->suffix );
699 xmlprops_set_property_i( props, EXMLPROP_RDN_INDEX, ctl->rdnIndex );
700 xmlprops_set_property_b( props, EXMLPROP_USE_DN, ctl->useDN );
701 xmlprops_set_property_b( props, EXMLPROP_EXCL_EMAIL, ctl->excludeEMail );
702 if (xmlprops_save_file( props ) != MGU_SUCCESS)
703 g_warning("can't save settings");
704 xmlprops_free( props );
708 * ============================================================================
709 * End of Source.
710 * ============================================================================