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