Normalize Webcal name capitalization everywhere (instead of WebCal, webCal,
[claws.git] / src / exportldif.c
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
42
43 #ifdef MKDIR_TAKES_ONE_ARG
44 #undef mkdir
45 #define mkdir(a,b) mkdir(a)
46 #endif
47
48 #define DFL_DIR_CLAWS_OUT  "claws-mail-out"
49 #define DFL_FILE_CLAWS_OUT "addressbook.ldif"
50
51 #define FMT_BUFSIZE           2048
52 #define XML_BUFSIZE           2048
53
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"
62
63 static gchar *_attrName_UID_   = "uid";
64 static gchar *_attrName_DName_ = "cn";
65 static gchar *_attrName_EMail_ = "mail";
66
67 /**
68  * Create initialized LDIF export control object.
69  * \return Initialized export control data.
70  */
71 ExportLdifCtl *exportldif_create( void ) {
72         ExportLdifCtl *ctl = g_new0( ExportLdifCtl, 1 );
73
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 );
85
86         return ctl;
87 }
88
89 /**
90  * Free up object by releasing internal memory.
91  * \return ctl Export control data.
92  */
93 void exportldif_free( ExportLdifCtl *ctl ) {
94         cm_return_if_fail( ctl != NULL );
95
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 );
101
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;
112
113         /* Now release object */
114         g_free( ctl );
115 }
116
117 /**
118  * Specify suffix to be used for creating DN entries.
119  * \param ctl   Export control data.
120  * \param value Suffix.
121  */
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 );
126 }
127
128 /**
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>
137  */
138 void exportldif_set_rdn( ExportLdifCtl *ctl, const gint value ) {
139         cm_return_if_fail( ctl != NULL );
140         ctl->rdnIndex = value;
141 }
142
143 /**
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.
148  */
149 void exportldif_set_use_dn( ExportLdifCtl *ctl, const gboolean value ) {
150         cm_return_if_fail( ctl != NULL );
151         ctl->useDN = value;
152 }
153
154 /**
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.
158  */
159 void exportldif_set_exclude_email( ExportLdifCtl *ctl, const gboolean value ) {
160         cm_return_if_fail( ctl != NULL );
161         ctl->excludeEMail = value;
162 }
163
164 /**
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.
168  */
169 static gchar *exportldif_fmt_value( gchar *value ) {
170         gchar *dupval;
171         gchar *src;
172         gchar *dest;
173         gchar ch;
174
175         /* Duplicate incoming value */
176         dest = dupval = g_strdup( value );
177
178         /* Copy characters, ignoring commas */
179         src = value;
180         while( *src ) {
181                 ch = *src;
182                 if( ch != ',' ) {
183                         *dest = ch;
184                         dest++;
185                 }
186                 src++;
187         }
188         *dest = '\0';
189         return dupval;
190 }
191
192 /**
193  * Build DN for entry.
194  * \param  ctl    Export control data.
195  * \param  person Person to format.
196  * \return Formatted DN entry.
197  */
198 static gchar *exportldif_fmt_dn(
199                 ExportLdifCtl *ctl, const ItemPerson *person )
200 {
201         gchar buf[ FMT_BUFSIZE + 1 ];
202         gchar *retVal = NULL;
203         gchar *attr = NULL;
204         gchar *value = NULL;
205         gchar *dupval = NULL;
206
207         /* Process RDN */
208         *buf = '\0';
209         if( ctl->rdnIndex == EXPORT_LDIF_ID_UID ) {
210                 attr = _attrName_UID_;
211                 value = ADDRITEM_ID( person );
212         }
213         else if( ctl->rdnIndex == EXPORT_LDIF_ID_DNAME ) {
214                 attr = _attrName_DName_;
215                 value = ADDRITEM_NAME( person );
216                 dupval = exportldif_fmt_value( value );
217         }
218         else if( ctl->rdnIndex == EXPORT_LDIF_ID_EMAIL ) {
219                 GList *node;
220
221                 node = person->listEMail;
222                 if( node ) {
223                         ItemEMail *email = node->data;
224
225                         attr = _attrName_EMail_;
226                         value = email->address;
227                         dupval = exportldif_fmt_value( value );
228                 }
229         }
230
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 );
241                                 }
242                                 else {
243                                         /* Use original value */
244                                         strncat( buf, value, FMT_BUFSIZE - strlen(buf) );
245                                 }
246
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) );
252                                         }
253                                 }
254
255                                 retVal = g_strdup( buf );
256                         }
257                 }
258         }
259         return retVal;
260 }
261
262 /**
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.
267  */
268 static gchar *exportldif_find_dn(
269                         ExportLdifCtl *ctl, const ItemPerson *person )
270 {
271         gchar *retVal = NULL;
272         const GList *node;
273
274         node = person->listAttrib;
275         while( node ) {
276                 UserAttribute *attrib = node->data;
277
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;
282                 }
283         }
284         return retVal;
285 }
286
287 /**
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.
292  */
293 static gboolean exportldif_fmt_email( const ItemPerson *person, FILE *stream ) {
294         gboolean retVal = FALSE;
295         const GList *node;
296
297         node = person->listEMail;
298         while( node ) {
299                 ItemEMail *email = node->data;
300
301                 node = g_list_next( node );
302                 ldif_write_value( stream, LDIF_TAG_EMAIL, email->address );
303                 retVal = TRUE;
304         }
305         return retVal;
306 }
307
308 /**
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.
312  */
313 static gboolean exportldif_test_email( const ItemPerson *person )
314 {
315         gboolean retVal = FALSE;
316         const GList *node;
317
318         node = person->listEMail;
319         while( node ) {
320                 ItemEMail *email = node->data;
321
322                 node = g_list_next( node );
323                 if( email->address ) {
324                         if( strlen( email->address ) > 0 ) {
325                                 retVal = TRUE;
326                                 break;
327                         }
328                 }
329                 retVal = TRUE;
330         }
331         return retVal;
332 }
333
334 /**
335  * Format other attributes for person.
336  * \param person ItemPerson.
337  * \param stream Output stream.
338  */
339 static void exportldif_fmt_other_attributes(ItemPerson* person, FILE* stream) {
340     UserAttribute* attr;
341     GList* attrList = NULL;
342     gchar* attrib;
343
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
353              */
354             attrib = g_strdup_printf("# %s", attr->name);
355         }
356         else {
357             attrib = g_strdup(attr->name);
358         }
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);
363     }
364     debug_print("-------------------------------\n");
365 }
366
367 /**
368  * Find persons displayName.
369  * \param person ItemPerson.
370  * \return displayName.
371  */
372 static gchar* exportldif_find_displayName(ItemPerson* person) {
373         gchar* displayName;
374
375         if (! person)
376             return NULL;
377         
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;
383 }
384
385 /**
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.
391  */
392 static gboolean exportldif_fmt_person(
393                 ExportLdifCtl *ctl, FILE *stream, const ItemFolder *folder )
394 {
395         gboolean retVal = TRUE;
396         const GList *node;
397         gchar* sn = NULL;
398         gchar* displayName = NULL;
399
400         if( folder->listPerson == NULL ) return retVal;
401
402         node = folder->listPerson;
403         while( node ) {
404                 AddrItemObject *aio = node->data;
405                 node = g_list_next( node );
406
407                 if( aio && aio->type == ITEMTYPE_PERSON ) {
408                         ItemPerson *person = ( ItemPerson * ) aio;
409                         gboolean classPerson = FALSE;
410                         gboolean classInetP = FALSE;
411                         gchar *dn = NULL;
412
413                         /* Check for E-Mail */
414                         if( exportldif_test_email( person ) ) {
415                                 classInetP = TRUE;
416                         }
417                         else {
418                                 /* Bail if no E-Mail address */
419                                 if( ctl->excludeEMail ) continue;
420                         }
421
422                         /* Format DN */
423                         if( ctl->useDN ) {
424                                 dn = exportldif_find_dn( ctl, person );
425                         }
426                         if( dn == NULL ) {
427                                 dn = exportldif_fmt_dn( ctl, person );
428                         }
429                         if( dn == NULL ) continue;
430                         ldif_write_value( stream, LDIF_TAG_DN, dn );
431                         g_free( dn );
432
433                         /*
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.
438                          */
439                         if( person->lastName ) {
440                                 if( strlen( person->lastName ) > 0 ) {
441                                         classPerson = TRUE;
442                                         classInetP = TRUE;
443                                 }
444                         }
445
446                         if( classPerson ) {
447                                 ldif_write_value( stream,
448                                         LDIF_TAG_OBJECTCLASS, LDIF_CLASS_PERSON );
449                         }
450                         if( classInetP ) {
451                                 ldif_write_value( stream,
452                                         LDIF_TAG_OBJECTCLASS, LDIF_CLASS_INET_PERSON );
453                         }
454
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");
463                                 }
464                         }
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 );
471
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;
479
480                         /* Format E-Mail */
481                         exportldif_fmt_email( person, stream );
482                         
483                         /* Handle other attributes */
484                         exportldif_fmt_other_attributes(person, stream);
485
486                         /* End record */
487                         ldif_write_eor( stream );
488
489                         retVal = FALSE;
490                 }
491         }
492
493         return retVal;
494 }
495
496 /**
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.
502  */
503 static void exportldif_fmt_folder(
504                 ExportLdifCtl *ctl, FILE *stream, const ItemFolder *folder )
505 {
506         const GList *node;
507
508         /* Export entries in this folder */
509         exportldif_fmt_person( ctl, stream, folder );
510
511         /* Export entries in sub-folders */
512         node = folder->listFolder;
513         while( node ) {
514                 AddrItemObject *aio = node->data;
515
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 );
520                 }
521         }
522 }
523
524 /**
525  * Export address book to LDIF file.
526  * \param  ctl   Export control data.
527  * \param  cache Address book/data source cache.
528  * \return Status.
529  */
530 void exportldif_process( ExportLdifCtl *ctl, AddressCache *cache )
531 {
532         ItemFolder *rootFolder;
533         FILE *ldifFile;
534
535         ldifFile = g_fopen( ctl->path, "wb" );
536         if( ! ldifFile ) {
537                 /* Cannot open file */
538                 ctl->retVal = MGU_OPEN_FILE;
539                 return;
540         }
541
542         rootFolder = cache->rootFolder;
543         exportldif_fmt_folder( ctl, ldifFile, rootFolder );
544         fclose( ldifFile );
545         ctl->retVal = MGU_SUCCESS;
546 }
547
548 /**
549  * Build full export file specification.
550  * \param ctl  Export control data.
551  */
552 static void exportldif_build_filespec( ExportLdifCtl *ctl ) {
553         gchar *fileSpec;
554
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 );
559 }
560
561 /**
562  * Parse directory and filename from full export file specification.
563  * \param ctl      Export control data.
564  * \param fileSpec File spec.
565  */
566 void exportldif_parse_filespec( ExportLdifCtl *ctl, gchar *fileSpec ) {
567         gchar *t;
568         gchar *base = g_path_get_basename(fileSpec);
569
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 );
577 }
578
579 /**
580  * Create output directory.
581  * \param  ctl Export control data.
582  * \return TRUE if directory created.
583  */
584 gboolean exportldif_create_dir( ExportLdifCtl *ctl ) {
585         gboolean retVal = FALSE;
586
587         ctl->rcCreate = 0;
588         if( mkdir( ctl->dirOutput, S_IRWXU ) == 0 ) {
589                 retVal = TRUE;
590         }
591         else {
592                 ctl->rcCreate = errno;
593         }
594         return retVal;
595 }
596
597 /**
598  * Retrieve create directory error message.
599  * \param  ctl Export control data.
600  * \return Message.
601  */
602 gchar *exportldif_get_create_msg( ExportLdifCtl *ctl ) {
603         gchar *msg;
604
605         if( ctl->rcCreate == EEXIST ) {
606                 msg = _( "Name already exists but is not a directory." );
607         }
608         else if( ctl->rcCreate == EACCES ) {
609                 msg = _( "No permissions to create directory." );
610         }
611         else if( ctl->rcCreate == ENAMETOOLONG ) {
612                 msg = _( "Name is too long." );
613         }
614         else {
615                 msg = _( "Not specified." );
616         }
617         return msg;
618 }
619
620 /**
621  * Set default values.
622  * \param  ctl Export control data.
623  */
624 static void exportldif_default_values( ExportLdifCtl *ctl ) {
625         gchar *str;
626
627         str = g_strconcat(
628                 get_home_dir(), G_DIR_SEPARATOR_S,
629                 DFL_DIR_CLAWS_OUT, NULL );
630
631         ctl->dirOutput = mgu_replace_string( ctl->dirOutput, str );
632         g_free( str );
633
634         ctl->fileLdif =
635                 mgu_replace_string( ctl->fileLdif, DFL_FILE_CLAWS_OUT );
636         ctl->suffix = mgu_replace_string( ctl->suffix, "" );
637
638         ctl->rdnIndex = EXPORT_LDIF_ID_UID;
639         ctl->useDN = FALSE;
640         ctl->retVal = MGU_SUCCESS;
641 }
642
643 /**
644  * Load settings from XML properties file.
645  * \param  ctl Export control data.
646  */
647 void exportldif_load_settings( ExportLdifCtl *ctl ) {
648         XmlProperty *props;
649         gint rc;
650         gchar buf[ XML_BUFSIZE ];
651
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 );
660
661                 *buf = '\0';
662                 xmlprops_get_property_s( props, EXMLPROP_FILE, buf );
663                 ctl->fileLdif = mgu_replace_string( ctl->fileLdif, buf );
664
665                 *buf = '\0';
666                 xmlprops_get_property_s( props, EXMLPROP_SUFFIX, buf );
667                 ctl->suffix = mgu_replace_string( ctl->suffix, buf );
668
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 );
675         }
676         else {
677                 /* Set default values */
678                 exportldif_default_values( ctl );
679         }
680         exportldif_build_filespec( ctl );
681         /* exportldif_print( ctl, stdout ); */
682
683         xmlprops_free( props );
684 }
685
686 /**
687  * Save settings to XML properties file.
688  * \param  ctl Export control data.
689  */
690 void exportldif_save_settings( ExportLdifCtl *ctl ) {
691         XmlProperty *props;
692
693         props = xmlprops_create();
694         xmlprops_set_path( props, ctl->settingsFile );
695
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 );
705 }
706
707 /*
708  * ============================================================================
709  * End of Source.
710  * ============================================================================
711  */
712
713