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