2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 2003-2022 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.
25 #include "claws-features.h"
34 #include <glib/gi18n.h>
38 #include "exportldif.h"
41 #include "file-utils.h"
44 #ifdef MKDIR_TAKES_ONE_ARG
46 #define mkdir(a,b) mkdir(a)
49 #define DFL_DIR_CLAWS_OUT "claws-mail-out"
50 #define DFL_FILE_CLAWS_OUT "addressbook.ldif"
52 #define FMT_BUFSIZE 2048
53 #define XML_BUFSIZE 2048
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"
64 static gchar
*_attrName_UID_
= "uid";
65 static gchar
*_attrName_DName_
= "cn";
66 static gchar
*_attrName_EMail_
= "mail";
69 * Create initialized LDIF export control object.
70 * \return Initialized export control data.
72 ExportLdifCtl
*exportldif_create( void ) {
73 ExportLdifCtl
*ctl
= g_new0( ExportLdifCtl
, 1 );
76 ctl
->dirOutput
= NULL
;
79 ctl
->rdnIndex
= EXPORT_LDIF_ID_UID
;
81 ctl
->excludeEMail
= TRUE
;
82 ctl
->retVal
= MGU_SUCCESS
;
84 ctl
->settingsFile
= g_strconcat(
85 get_rc_dir(), G_DIR_SEPARATOR_S
, EXML_PROPFILE_NAME
, NULL
);
91 * Free up object by releasing internal memory.
92 * \return ctl Export control data.
94 void exportldif_free( ExportLdifCtl
*ctl
) {
95 cm_return_if_fail( ctl
!= NULL
);
98 g_free( ctl
->fileLdif
);
99 g_free( ctl
->dirOutput
);
100 g_free( ctl
->suffix
);
101 g_free( ctl
->settingsFile
);
105 ctl
->dirOutput
= NULL
;
106 ctl
->fileLdif
= NULL
;
108 ctl
->rdnIndex
= EXPORT_LDIF_ID_UID
;
110 ctl
->excludeEMail
= FALSE
;
111 ctl
->retVal
= MGU_SUCCESS
;
114 /* Now release object */
119 * Specify suffix to be used for creating DN entries.
120 * \param ctl Export control data.
121 * \param value Suffix.
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
);
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:
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>
139 void exportldif_set_rdn( ExportLdifCtl
*ctl
, const gint value
) {
140 cm_return_if_fail( ctl
!= NULL
);
141 ctl
->rdnIndex
= value
;
145 * Specify that <code>DN</code> attribute, if present, should be used as the
147 * \param ctl Export control data.
148 * \param value <i>TRUE</i> if DN should be used.
150 void exportldif_set_use_dn( ExportLdifCtl
*ctl
, const gboolean value
) {
151 cm_return_if_fail( ctl
!= NULL
);
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.
160 void exportldif_set_exclude_email( ExportLdifCtl
*ctl
, const gboolean value
) {
161 cm_return_if_fail( ctl
!= NULL
);
162 ctl
->excludeEMail
= value
;
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.
170 static gchar
*exportldif_fmt_value( gchar
*value
) {
176 /* Duplicate incoming value */
177 dest
= dupval
= g_strdup( value
);
179 /* Copy characters, ignoring commas */
194 * Build DN for entry.
195 * \param ctl Export control data.
196 * \param person Person to format.
197 * \return Formatted DN entry.
199 static gchar
*exportldif_fmt_dn(
200 ExportLdifCtl
*ctl
, const ItemPerson
*person
)
202 gchar buf
[ FMT_BUFSIZE
+ 1 ];
203 gchar
*retVal
= NULL
;
206 gchar
*dupval
= NULL
;
210 if( ctl
->rdnIndex
== EXPORT_LDIF_ID_UID
) {
211 attr
= _attrName_UID_
;
212 value
= ADDRITEM_ID( person
);
214 else if( ctl
->rdnIndex
== EXPORT_LDIF_ID_DNAME
) {
215 attr
= _attrName_DName_
;
216 value
= ADDRITEM_NAME( person
);
217 dupval
= exportldif_fmt_value( value
);
219 else if( ctl
->rdnIndex
== EXPORT_LDIF_ID_EMAIL
) {
222 node
= person
->listEMail
;
224 ItemEMail
*email
= node
->data
;
226 attr
= _attrName_EMail_
;
227 value
= email
->address
;
228 dupval
= exportldif_fmt_value( value
);
235 if( strlen( value
) > 0 ) {
236 strncat( buf
, attr
, FMT_BUFSIZE
- strlen(buf
) );
237 strncat( buf
, "=", FMT_BUFSIZE
- strlen(buf
) );
239 /* Format and free duplicated value */
240 strncat( buf
, dupval
, FMT_BUFSIZE
- strlen(buf
) );
244 /* Use original value */
245 strncat( buf
, value
, FMT_BUFSIZE
- strlen(buf
) );
250 if( strlen( ctl
->suffix
) > 0 ) {
251 strncat( buf
, ",", FMT_BUFSIZE
- strlen(buf
) );
252 strncat( buf
, ctl
->suffix
, FMT_BUFSIZE
- strlen(buf
) );
256 retVal
= g_strdup( buf
);
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.
269 static gchar
*exportldif_find_dn(
270 ExportLdifCtl
*ctl
, const ItemPerson
*person
)
272 gchar
*retVal
= NULL
;
275 node
= person
->listAttrib
;
277 UserAttribute
*attrib
= node
->data
;
279 node
= g_list_next( node
);
280 if( g_utf8_collate( attrib
->name
, LDIF_TAG_DN
) == 0 ) {
281 retVal
= g_strdup( attrib
->value
);
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.
294 static gboolean
exportldif_fmt_email( const ItemPerson
*person
, FILE *stream
) {
295 gboolean retVal
= FALSE
;
298 node
= person
->listEMail
;
300 ItemEMail
*email
= node
->data
;
302 node
= g_list_next( node
);
303 ldif_write_value( stream
, LDIF_TAG_EMAIL
, email
->address
);
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.
314 static gboolean
exportldif_test_email( const ItemPerson
*person
)
316 gboolean retVal
= FALSE
;
319 node
= person
->listEMail
;
321 ItemEMail
*email
= node
->data
;
323 node
= g_list_next( node
);
324 if( email
->address
) {
325 if( strlen( email
->address
) > 0 ) {
336 * Format other attributes for person.
337 * \param person ItemPerson.
338 * \param stream Output stream.
340 static void exportldif_fmt_other_attributes(ItemPerson
* person
, FILE* stream
) {
342 GList
* attrList
= NULL
;
347 debug_print("cn: %s\n-----------------------------\n", ADDRITEM_NAME(person
));
348 attrList
= person
->listAttrib
;
350 attr
= (UserAttribute
*) attrList
->data
;
352 /* Native address book which does not conform to
355 attrib
= g_strdup_printf("# %s", attr
->name
);
358 attrib
= g_strdup(attr
->name
);
360 debug_print("name: %s\nvalue: %s\n", attrib
, attr
->value
);
361 ldif_write_value(stream
, attrib
, attr
->value
);
363 attrList
= g_list_next(attrList
);
365 debug_print("-------------------------------\n");
369 * Find persons displayName.
370 * \param person ItemPerson.
371 * \return displayName.
373 static gchar
* exportldif_find_displayName(ItemPerson
* person
) {
379 if (person
->nickName
&& strlen(person
->nickName
) > 0)
380 displayName
= g_strdup(person
->nickName
);
382 displayName
= g_strdup(ADDRITEM_NAME(person
));
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.
393 static gboolean
exportldif_fmt_person(
394 ExportLdifCtl
*ctl
, FILE *stream
, const ItemFolder
*folder
)
396 gboolean retVal
= TRUE
;
399 gchar
* displayName
= NULL
;
401 if( folder
->listPerson
== NULL
) return retVal
;
403 node
= folder
->listPerson
;
405 AddrItemObject
*aio
= node
->data
;
406 node
= g_list_next( node
);
408 if( aio
&& aio
->type
== ITEMTYPE_PERSON
) {
409 ItemPerson
*person
= ( ItemPerson
* ) aio
;
410 gboolean classPerson
= FALSE
;
411 gboolean classInetP
= FALSE
;
414 /* Check for E-Mail */
415 if( exportldif_test_email( person
) ) {
419 /* Bail if no E-Mail address */
420 if( ctl
->excludeEMail
) continue;
425 dn
= exportldif_find_dn( ctl
, person
);
428 dn
= exportldif_fmt_dn( ctl
, person
);
430 if( dn
== NULL
) continue;
431 ldif_write_value( stream
, LDIF_TAG_DN
, dn
);
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.
440 if( person
->lastName
) {
441 if( strlen( person
->lastName
) > 0 ) {
448 ldif_write_value( stream
,
449 LDIF_TAG_OBJECTCLASS
, LDIF_CLASS_PERSON
);
452 ldif_write_value( stream
,
453 LDIF_TAG_OBJECTCLASS
, LDIF_CLASS_INET_PERSON
);
456 /* Format person attributes */
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) {
463 sn
= g_strdup("Some SN");
467 stream
, LDIF_TAG_LASTNAME
, sn
);
471 stream
, LDIF_TAG_FIRSTNAME
, person
->firstName
);
473 if (! person
->externalID
)
474 displayName
= exportldif_find_displayName(person
);
476 displayName
= g_strdup(person
->nickName
);
477 ldif_write_value(stream
, LDIF_TAG_NICKNAME
, displayName
);
482 exportldif_fmt_email( person
, stream
);
484 /* Handle other attributes */
485 exportldif_fmt_other_attributes(person
, stream
);
488 ldif_write_eor( stream
);
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.
504 static void exportldif_fmt_folder(
505 ExportLdifCtl
*ctl
, FILE *stream
, const ItemFolder
*folder
)
509 /* Export entries in this folder */
510 exportldif_fmt_person( ctl
, stream
, folder
);
512 /* Export entries in subfolders */
513 node
= folder
->listFolder
;
515 AddrItemObject
*aio
= node
->data
;
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
);
526 * Export address book to LDIF file.
527 * \param ctl Export control data.
528 * \param cache Address book/data source cache.
531 void exportldif_process( ExportLdifCtl
*ctl
, AddressCache
*cache
)
533 ItemFolder
*rootFolder
;
536 ldifFile
= claws_fopen( ctl
->path
, "wb" );
538 /* Cannot open file */
539 ctl
->retVal
= MGU_OPEN_FILE
;
543 rootFolder
= cache
->rootFolder
;
544 exportldif_fmt_folder( ctl
, ldifFile
, rootFolder
);
545 claws_safe_fclose( ldifFile
);
546 ctl
->retVal
= MGU_SUCCESS
;
550 * Build full export file specification.
551 * \param ctl Export control data.
553 static void exportldif_build_filespec( ExportLdifCtl
*ctl
) {
556 fileSpec
= g_strconcat(
557 ctl
->dirOutput
, G_DIR_SEPARATOR_S
, ctl
->fileLdif
, NULL
);
558 ctl
->path
= mgu_replace_string( ctl
->path
, fileSpec
);
563 * Parse directory and filename from full export file specification.
564 * \param ctl Export control data.
565 * \param fileSpec File spec.
567 void exportldif_parse_filespec( ExportLdifCtl
*ctl
, gchar
*fileSpec
) {
569 gchar
*base
= g_path_get_basename(fileSpec
);
572 mgu_replace_string( ctl
->fileLdif
, base
);
574 t
= g_path_get_dirname( fileSpec
);
575 ctl
->dirOutput
= mgu_replace_string( ctl
->dirOutput
, t
);
577 ctl
->path
= mgu_replace_string( ctl
->path
, fileSpec
);
581 * Create output directory.
582 * \param ctl Export control data.
583 * \return TRUE if directory created.
585 gboolean
exportldif_create_dir( ExportLdifCtl
*ctl
) {
586 gboolean retVal
= FALSE
;
589 if( mkdir( ctl
->dirOutput
, S_IRWXU
) == 0 ) {
593 ctl
->rcCreate
= errno
;
599 * Retrieve create directory error message.
600 * \param ctl Export control data.
603 gchar
*exportldif_get_create_msg( ExportLdifCtl
*ctl
) {
606 if( ctl
->rcCreate
== EEXIST
) {
607 msg
= _( "Name already exists but is not a directory." );
609 else if( ctl
->rcCreate
== EACCES
) {
610 msg
= _( "No permissions to create directory." );
612 else if( ctl
->rcCreate
== ENAMETOOLONG
) {
613 msg
= _( "Name is too long." );
616 msg
= _( "Not specified." );
622 * Set default values.
623 * \param ctl Export control data.
625 static void exportldif_default_values( ExportLdifCtl
*ctl
) {
629 get_home_dir(), G_DIR_SEPARATOR_S
,
630 DFL_DIR_CLAWS_OUT
, NULL
);
632 ctl
->dirOutput
= mgu_replace_string( ctl
->dirOutput
, str
);
636 mgu_replace_string( ctl
->fileLdif
, DFL_FILE_CLAWS_OUT
);
637 ctl
->suffix
= mgu_replace_string( ctl
->suffix
, "" );
639 ctl
->rdnIndex
= EXPORT_LDIF_ID_UID
;
641 ctl
->retVal
= MGU_SUCCESS
;
645 * Load settings from XML properties file.
646 * \param ctl Export control data.
648 void exportldif_load_settings( ExportLdifCtl
*ctl
) {
651 gchar buf
[ XML_BUFSIZE
];
653 props
= xmlprops_create();
654 xmlprops_set_path( props
, ctl
->settingsFile
);
655 rc
= xmlprops_load_file( props
);
659 xmlprops_get_property_s( props
, EXMLPROP_DIRECTORY
, buf
);
660 ctl
->dirOutput
= mgu_replace_string( ctl
->dirOutput
, buf
);
663 xmlprops_get_property_s( props
, EXMLPROP_FILE
, buf
);
664 ctl
->fileLdif
= mgu_replace_string( ctl
->fileLdif
, buf
);
667 xmlprops_get_property_s( props
, EXMLPROP_SUFFIX
, buf
);
668 ctl
->suffix
= mgu_replace_string( ctl
->suffix
, buf
);
671 xmlprops_get_property_i( props
, EXMLPROP_RDN_INDEX
);
673 xmlprops_get_property_b( props
, EXMLPROP_USE_DN
);
675 xmlprops_get_property_b( props
, EXMLPROP_EXCL_EMAIL
);
678 /* Set default values */
679 exportldif_default_values( ctl
);
681 exportldif_build_filespec( ctl
);
682 /* exportldif_print( ctl, stdout ); */
684 xmlprops_free( props
);
688 * Save settings to XML properties file.
689 * \param ctl Export control data.
691 void exportldif_save_settings( ExportLdifCtl
*ctl
) {
694 props
= xmlprops_create();
695 xmlprops_set_path( props
, ctl
->settingsFile
);
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
);
709 * ============================================================================
711 * ============================================================================