2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 2003-2024 the Claws Mail team and Match Grun
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 #define DFL_DIR_CLAWS_OUT "claws-mail-out"
45 #define DFL_FILE_CLAWS_OUT "addressbook.ldif"
47 #define FMT_BUFSIZE 2048
48 #define XML_BUFSIZE 2048
50 /* Settings - properties */
51 #define EXML_PROPFILE_NAME "exportldif.xml"
52 #define EXMLPROP_DIRECTORY "directory"
53 #define EXMLPROP_FILE "file"
54 #define EXMLPROP_SUFFIX "suffix"
55 #define EXMLPROP_RDN_INDEX "rdn"
56 #define EXMLPROP_USE_DN "use-dn"
57 #define EXMLPROP_EXCL_EMAIL "exclude-mail"
59 static gchar
*_attrName_UID_
= "uid";
60 static gchar
*_attrName_DName_
= "cn";
61 static gchar
*_attrName_EMail_
= "mail";
64 * Create initialized LDIF export control object.
65 * \return Initialized export control data.
67 ExportLdifCtl
*exportldif_create( void ) {
68 ExportLdifCtl
*ctl
= g_new0( ExportLdifCtl
, 1 );
71 ctl
->dirOutput
= NULL
;
74 ctl
->rdnIndex
= EXPORT_LDIF_ID_UID
;
76 ctl
->excludeEMail
= TRUE
;
77 ctl
->retVal
= MGU_SUCCESS
;
79 ctl
->settingsFile
= g_strconcat(
80 get_rc_dir(), G_DIR_SEPARATOR_S
, EXML_PROPFILE_NAME
, NULL
);
86 * Free up object by releasing internal memory.
87 * \return ctl Export control data.
89 void exportldif_free( ExportLdifCtl
*ctl
) {
90 cm_return_if_fail( ctl
!= NULL
);
93 g_free( ctl
->fileLdif
);
94 g_free( ctl
->dirOutput
);
95 g_free( ctl
->suffix
);
96 g_free( ctl
->settingsFile
);
100 ctl
->dirOutput
= NULL
;
101 ctl
->fileLdif
= NULL
;
103 ctl
->rdnIndex
= EXPORT_LDIF_ID_UID
;
105 ctl
->excludeEMail
= FALSE
;
106 ctl
->retVal
= MGU_SUCCESS
;
109 /* Now release object */
114 * Specify suffix to be used for creating DN entries.
115 * \param ctl Export control data.
116 * \param value Suffix.
118 void exportldif_set_suffix( ExportLdifCtl
*ctl
, const char *value
) {
119 cm_return_if_fail( ctl
!= NULL
);
120 ctl
->suffix
= mgu_replace_string( ctl
->suffix
, value
);
121 g_strstrip( ctl
->suffix
);
125 * Specify index of variable to be used for creating RDN entries.
126 * \param ctl Export control data.
127 * \param value Index to variable, as follows:
129 * <li><code>EXPORT_LDIF_ID_UID</code> - Use Claws Mail UID.</li>
130 * <li><code>EXPORT_LDIF_ID_DNAME</code> - Use Claws Mail display name.</li>
131 * <li><code>EXPORT_LDIF_ID_EMAIL</code> - Use first Email address.</li>
134 void exportldif_set_rdn( ExportLdifCtl
*ctl
, const gint value
) {
135 cm_return_if_fail( ctl
!= NULL
);
136 ctl
->rdnIndex
= value
;
140 * Specify that <code>DN</code> attribute, if present, should be used as the
142 * \param ctl Export control data.
143 * \param value <i>TRUE</i> if DN should be used.
145 void exportldif_set_use_dn( ExportLdifCtl
*ctl
, const gboolean value
) {
146 cm_return_if_fail( ctl
!= NULL
);
151 * Specify that records without E-Mail addresses should be excluded.
152 * \param ctl Export control data.
153 * \param value <i>TRUE</i> if records without E-Mail should be excluded.
155 void exportldif_set_exclude_email( ExportLdifCtl
*ctl
, const gboolean value
) {
156 cm_return_if_fail( ctl
!= NULL
);
157 ctl
->excludeEMail
= value
;
161 * Format LDAP value name with no embedded commas.
162 * \param value Data value to format.
163 * \return Formatted string, should be freed after use.
165 static gchar
*exportldif_fmt_value( gchar
*value
) {
171 /* Duplicate incoming value */
172 dest
= dupval
= g_strdup( value
);
174 /* Copy characters, ignoring commas */
189 * Build DN for entry.
190 * \param ctl Export control data.
191 * \param person Person to format.
192 * \return Formatted DN entry.
194 static gchar
*exportldif_fmt_dn(
195 ExportLdifCtl
*ctl
, const ItemPerson
*person
)
197 gchar buf
[ FMT_BUFSIZE
+ 1 ];
198 gchar
*retVal
= NULL
;
201 gchar
*dupval
= NULL
;
205 if( ctl
->rdnIndex
== EXPORT_LDIF_ID_UID
) {
206 attr
= _attrName_UID_
;
207 value
= ADDRITEM_ID( person
);
209 else if( ctl
->rdnIndex
== EXPORT_LDIF_ID_DNAME
) {
210 attr
= _attrName_DName_
;
211 value
= ADDRITEM_NAME( person
);
212 dupval
= exportldif_fmt_value( value
);
214 else if( ctl
->rdnIndex
== EXPORT_LDIF_ID_EMAIL
) {
217 node
= person
->listEMail
;
219 ItemEMail
*email
= node
->data
;
221 attr
= _attrName_EMail_
;
222 value
= email
->address
;
223 dupval
= exportldif_fmt_value( value
);
230 if( strlen( value
) > 0 ) {
231 strncat( buf
, attr
, FMT_BUFSIZE
- strlen(buf
) );
232 strncat( buf
, "=", FMT_BUFSIZE
- strlen(buf
) );
234 /* Format and free duplicated value */
235 strncat( buf
, dupval
, FMT_BUFSIZE
- strlen(buf
) );
239 /* Use original value */
240 strncat( buf
, value
, FMT_BUFSIZE
- strlen(buf
) );
245 if( strlen( ctl
->suffix
) > 0 ) {
246 strncat( buf
, ",", FMT_BUFSIZE
- strlen(buf
) );
247 strncat( buf
, ctl
->suffix
, FMT_BUFSIZE
- strlen(buf
) );
251 retVal
= g_strdup( buf
);
259 * Find DN by searching attribute list.
260 * \param ctl Export control data.
261 * \param person Person to format.
262 * \return Formatted DN entry, should be freed after use.
264 static gchar
*exportldif_find_dn(
265 ExportLdifCtl
*ctl
, const ItemPerson
*person
)
267 gchar
*retVal
= NULL
;
270 node
= person
->listAttrib
;
272 UserAttribute
*attrib
= node
->data
;
274 node
= g_list_next( node
);
275 if( g_utf8_collate( attrib
->name
, LDIF_TAG_DN
) == 0 ) {
276 retVal
= g_strdup( attrib
->value
);
284 * Format E-Mail entries for person.
285 * \param person Person to format.
286 * \param stream Output stream.
287 * \return <i>TRUE</i> if entry formatted.
289 static gboolean
exportldif_fmt_email( const ItemPerson
*person
, FILE *stream
) {
290 gboolean retVal
= FALSE
;
293 node
= person
->listEMail
;
295 ItemEMail
*email
= node
->data
;
297 node
= g_list_next( node
);
298 ldif_write_value( stream
, LDIF_TAG_EMAIL
, email
->address
);
305 * Test for E-Mail entries for person.
306 * \param person Person to test.
307 * \return <i>TRUE</i> if person has E-Mail address.
309 static gboolean
exportldif_test_email( const ItemPerson
*person
)
311 gboolean retVal
= FALSE
;
314 node
= person
->listEMail
;
316 ItemEMail
*email
= node
->data
;
318 node
= g_list_next( node
);
319 if( email
->address
) {
320 if( strlen( email
->address
) > 0 ) {
331 * Format other attributes for person.
332 * \param person ItemPerson.
333 * \param stream Output stream.
335 static void exportldif_fmt_other_attributes(ItemPerson
* person
, FILE* stream
) {
337 GList
* attrList
= NULL
;
342 debug_print("cn: %s\n-----------------------------\n", ADDRITEM_NAME(person
));
343 attrList
= person
->listAttrib
;
345 attr
= (UserAttribute
*) attrList
->data
;
347 /* Native address book which does not conform to
350 attrib
= g_strdup_printf("# %s", attr
->name
);
353 attrib
= g_strdup(attr
->name
);
355 debug_print("name: %s\nvalue: %s\n", attrib
, attr
->value
);
356 ldif_write_value(stream
, attrib
, attr
->value
);
358 attrList
= g_list_next(attrList
);
360 debug_print("-------------------------------\n");
364 * Find persons displayName.
365 * \param person ItemPerson.
366 * \return displayName.
368 static gchar
* exportldif_find_displayName(ItemPerson
* person
) {
374 if (person
->nickName
&& strlen(person
->nickName
) > 0)
375 displayName
= g_strdup(person
->nickName
);
377 displayName
= g_strdup(ADDRITEM_NAME(person
));
382 * Format persons in an address book folder.
383 * \param ctl Export control data.
384 * \param stream Output stream.
385 * \param folder Folder to format.
386 * \return <i>TRUE</i> if no persons were formatted.
388 static gboolean
exportldif_fmt_person(
389 ExportLdifCtl
*ctl
, FILE *stream
, const ItemFolder
*folder
)
391 gboolean retVal
= TRUE
;
394 gchar
* displayName
= NULL
;
396 if( folder
->listPerson
== NULL
) return retVal
;
398 node
= folder
->listPerson
;
400 AddrItemObject
*aio
= node
->data
;
401 node
= g_list_next( node
);
403 if( aio
&& aio
->type
== ITEMTYPE_PERSON
) {
404 ItemPerson
*person
= ( ItemPerson
* ) aio
;
405 gboolean classPerson
= FALSE
;
406 gboolean classInetP
= FALSE
;
409 /* Check for E-Mail */
410 if( exportldif_test_email( person
) ) {
414 /* Bail if no E-Mail address */
415 if( ctl
->excludeEMail
) continue;
420 dn
= exportldif_find_dn( ctl
, person
);
423 dn
= exportldif_fmt_dn( ctl
, person
);
425 if( dn
== NULL
) continue;
426 ldif_write_value( stream
, LDIF_TAG_DN
, dn
);
430 * Test for schema requirements. This is a simple
431 * test and does not trap all LDAP schema errors.
432 * These can be detected when the LDIF file is
433 * loaded into an LDAP server.
435 if( person
->lastName
) {
436 if( strlen( person
->lastName
) > 0 ) {
443 ldif_write_value( stream
,
444 LDIF_TAG_OBJECTCLASS
, LDIF_CLASS_PERSON
);
447 ldif_write_value( stream
,
448 LDIF_TAG_OBJECTCLASS
, LDIF_CLASS_INET_PERSON
);
451 /* Format person attributes */
453 stream
, LDIF_TAG_COMMONNAME
, ADDRITEM_NAME( person
) );
454 sn
= g_strdup(person
->lastName
);
455 if (classPerson
|| classInetP
) {
456 if(! sn
|| strcmp("", sn
) == 0 || strcmp(" ", sn
) == 0) {
458 sn
= g_strdup("Some SN");
462 stream
, LDIF_TAG_LASTNAME
, sn
);
466 stream
, LDIF_TAG_FIRSTNAME
, person
->firstName
);
468 if (! person
->externalID
)
469 displayName
= exportldif_find_displayName(person
);
471 displayName
= g_strdup(person
->nickName
);
472 ldif_write_value(stream
, LDIF_TAG_NICKNAME
, displayName
);
477 exportldif_fmt_email( person
, stream
);
479 /* Handle other attributes */
480 exportldif_fmt_other_attributes(person
, stream
);
483 ldif_write_eor( stream
);
493 * Format an address book folder.
494 * \param ctl Export control data.
495 * \param stream Output stream.
496 * \param folder Folder to format.
497 * \return <i>TRUE</i> if no persons were formatted.
499 static void exportldif_fmt_folder(
500 ExportLdifCtl
*ctl
, FILE *stream
, const ItemFolder
*folder
)
504 /* Export entries in this folder */
505 exportldif_fmt_person( ctl
, stream
, folder
);
507 /* Export entries in subfolders */
508 node
= folder
->listFolder
;
510 AddrItemObject
*aio
= node
->data
;
512 node
= g_list_next( node
);
513 if( aio
&& aio
->type
== ITEMTYPE_FOLDER
) {
514 ItemFolder
*subFolder
= ( ItemFolder
* ) aio
;
515 exportldif_fmt_folder( ctl
, stream
, subFolder
);
521 * Export address book to LDIF file.
522 * \param ctl Export control data.
523 * \param cache Address book/data source cache.
526 void exportldif_process( ExportLdifCtl
*ctl
, AddressCache
*cache
)
528 ItemFolder
*rootFolder
;
531 ldifFile
= claws_fopen( ctl
->path
, "wb" );
533 /* Cannot open file */
534 ctl
->retVal
= MGU_OPEN_FILE
;
538 rootFolder
= cache
->rootFolder
;
539 exportldif_fmt_folder( ctl
, ldifFile
, rootFolder
);
540 claws_safe_fclose( ldifFile
);
541 ctl
->retVal
= MGU_SUCCESS
;
545 * Build full export file specification.
546 * \param ctl Export control data.
548 static void exportldif_build_filespec( ExportLdifCtl
*ctl
) {
551 fileSpec
= g_strconcat(
552 ctl
->dirOutput
, G_DIR_SEPARATOR_S
, ctl
->fileLdif
, NULL
);
553 ctl
->path
= mgu_replace_string( ctl
->path
, fileSpec
);
558 * Parse directory and filename from full export file specification.
559 * \param ctl Export control data.
560 * \param fileSpec File spec.
562 void exportldif_parse_filespec( ExportLdifCtl
*ctl
, gchar
*fileSpec
) {
564 gchar
*base
= g_path_get_basename(fileSpec
);
567 mgu_replace_string( ctl
->fileLdif
, base
);
569 t
= g_path_get_dirname( fileSpec
);
570 ctl
->dirOutput
= mgu_replace_string( ctl
->dirOutput
, t
);
572 ctl
->path
= mgu_replace_string( ctl
->path
, fileSpec
);
576 * Create output directory.
577 * \param ctl Export control data.
578 * \return TRUE if directory created.
580 gboolean
exportldif_create_dir( ExportLdifCtl
*ctl
) {
581 gboolean retVal
= FALSE
;
584 if( g_mkdir( ctl
->dirOutput
, S_IRWXU
) == 0 ) {
588 ctl
->rcCreate
= errno
;
594 * Retrieve create directory error message.
595 * \param ctl Export control data.
598 gchar
*exportldif_get_create_msg( ExportLdifCtl
*ctl
) {
601 if( ctl
->rcCreate
== EEXIST
) {
602 msg
= _( "Name already exists but is not a directory." );
604 else if( ctl
->rcCreate
== EACCES
) {
605 msg
= _( "No permissions to create directory." );
607 else if( ctl
->rcCreate
== ENAMETOOLONG
) {
608 msg
= _( "Name is too long." );
611 msg
= _( "Not specified." );
617 * Set default values.
618 * \param ctl Export control data.
620 static void exportldif_default_values( ExportLdifCtl
*ctl
) {
624 get_home_dir(), G_DIR_SEPARATOR_S
,
625 DFL_DIR_CLAWS_OUT
, NULL
);
627 ctl
->dirOutput
= mgu_replace_string( ctl
->dirOutput
, str
);
631 mgu_replace_string( ctl
->fileLdif
, DFL_FILE_CLAWS_OUT
);
632 ctl
->suffix
= mgu_replace_string( ctl
->suffix
, "" );
634 ctl
->rdnIndex
= EXPORT_LDIF_ID_UID
;
636 ctl
->retVal
= MGU_SUCCESS
;
640 * Load settings from XML properties file.
641 * \param ctl Export control data.
643 void exportldif_load_settings( ExportLdifCtl
*ctl
) {
646 gchar buf
[ XML_BUFSIZE
];
648 props
= xmlprops_create();
649 xmlprops_set_path( props
, ctl
->settingsFile
);
650 rc
= xmlprops_load_file( props
);
654 xmlprops_get_property_s( props
, EXMLPROP_DIRECTORY
, buf
);
655 ctl
->dirOutput
= mgu_replace_string( ctl
->dirOutput
, buf
);
658 xmlprops_get_property_s( props
, EXMLPROP_FILE
, buf
);
659 ctl
->fileLdif
= mgu_replace_string( ctl
->fileLdif
, buf
);
662 xmlprops_get_property_s( props
, EXMLPROP_SUFFIX
, buf
);
663 ctl
->suffix
= mgu_replace_string( ctl
->suffix
, buf
);
666 xmlprops_get_property_i( props
, EXMLPROP_RDN_INDEX
);
668 xmlprops_get_property_b( props
, EXMLPROP_USE_DN
);
670 xmlprops_get_property_b( props
, EXMLPROP_EXCL_EMAIL
);
673 /* Set default values */
674 exportldif_default_values( ctl
);
676 exportldif_build_filespec( ctl
);
677 /* exportldif_print( ctl, stdout ); */
679 xmlprops_free( props
);
683 * Save settings to XML properties file.
684 * \param ctl Export control data.
686 void exportldif_save_settings( ExportLdifCtl
*ctl
) {
689 props
= xmlprops_create();
690 xmlprops_set_path( props
, ctl
->settingsFile
);
692 xmlprops_set_property( props
, EXMLPROP_DIRECTORY
, ctl
->dirOutput
);
693 xmlprops_set_property( props
, EXMLPROP_FILE
, ctl
->fileLdif
);
694 xmlprops_set_property( props
, EXMLPROP_SUFFIX
, ctl
->suffix
);
695 xmlprops_set_property_i( props
, EXMLPROP_RDN_INDEX
, ctl
->rdnIndex
);
696 xmlprops_set_property_b( props
, EXMLPROP_USE_DN
, ctl
->useDN
);
697 xmlprops_set_property_b( props
, EXMLPROP_EXCL_EMAIL
, ctl
->excludeEMail
);
698 if (xmlprops_save_file( props
) != MGU_SUCCESS
)
699 g_warning("can't save settings");
700 xmlprops_free( props
);
704 * ============================================================================
706 * ============================================================================