2012-10-04 [colin] 3.8.1cvs84
[claws.git] / src / mbox.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2012 Hiroyuki Yamamoto 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 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #include "claws-features.h"
23 #endif
24
25
26 #define _GNU_SOURCE
27 #include <stdio.h>
28
29 #include "defs.h"
30 #include <glib.h>
31 #include <glib/gi18n.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <string.h>
35 #include <fcntl.h>
36 #include <sys/file.h>
37 #include <ctype.h>
38 #include <time.h>
39 #include <errno.h>
40
41 #ifdef G_OS_WIN32
42 #  include <w32lib.h>
43 #endif
44
45 #include "mbox.h"
46 #include "procmsg.h"
47 #include "folder.h"
48 #include "prefs_common.h"
49 #include "prefs_account.h"
50 #include "account.h"
51 #include "utils.h"
52 #include "filtering.h"
53 #include "alertpanel.h"
54 #include "statusbar.h"
55
56 #define MSGBUFSIZE      8192
57
58 #ifdef HAVE_FGETS_UNLOCKED
59 #define SC_FGETS fgets_unlocked
60 #define SC_FPUTS fputs_unlocked
61 #define SC_FPUTC fputc_unlocked
62 #else
63 #define SC_FGETS fgets
64 #define SC_FPUTS fputs
65 #define SC_FPUTC fputc
66 #endif
67
68 #define FPUTS_TO_TMP_ABORT_IF_FAIL(s) \
69 { \
70         lines++; \
71         if (fputs(s, tmp_fp) == EOF) { \
72                 g_warning("can't write to temporary file\n"); \
73                 fclose(tmp_fp); \
74                 fclose(mbox_fp); \
75                 claws_unlink(tmp_file); \
76                 g_free(tmp_file); \
77                 return -1; \
78         } \
79 }
80
81 gint proc_mbox(FolderItem *dest, const gchar *mbox, gboolean apply_filter,
82                PrefsAccount *account)
83 /* return values: -1 error, >=0 number of msgs added */
84 {
85         FILE *mbox_fp;
86         gchar buf[MSGBUFSIZE];
87         gchar *tmp_file;
88         gint msgs = 0;
89         gint lines;
90         MsgInfo *msginfo;
91         gboolean more;
92         GSList *to_filter = NULL, *filtered = NULL, *unfiltered = NULL, *cur, *to_add = NULL;
93         gboolean printed = FALSE;
94         FolderItem *dropfolder;
95
96         cm_return_val_if_fail(dest != NULL, -1);
97         cm_return_val_if_fail(mbox != NULL, -1);
98
99         debug_print("Getting messages from %s into %s...\n", mbox, dest->path);
100
101         if ((mbox_fp = g_fopen(mbox, "rb")) == NULL) {
102                 FILE_OP_ERROR(mbox, "fopen");
103                 alertpanel_error(_("Could not open mbox file:\n%s\n"), mbox);
104                 return -1;
105         }
106
107         /* ignore empty lines on the head */
108         do {
109                 if (fgets(buf, sizeof(buf), mbox_fp) == NULL) {
110                         g_warning("can't read mbox file.\n");
111                         fclose(mbox_fp);
112                         return -1;
113                 }
114         } while (buf[0] == '\n' || buf[0] == '\r');
115
116         if (strncmp(buf, "From ", 5) != 0) {
117                 g_warning("invalid mbox format: %s\n", mbox);
118                 fclose(mbox_fp);
119                 return -1;
120         }
121
122         tmp_file = get_tmp_file();
123
124         folder_item_update_freeze();
125
126         if (apply_filter)
127                 dropfolder = folder_get_default_processing();
128         else
129                 dropfolder = dest;
130         
131         do {
132                 FILE *tmp_fp;
133                 gint empty_lines;
134                 gint msgnum;
135                 
136                 if (msgs > 0 && msgs%500 == 0) {
137                         if (printed)
138                                 statusbar_pop_all();
139                         statusbar_print_all(_("Importing from mbox... (%d mails imported)"), msgs);
140                         printed=TRUE;
141                         GTK_EVENTS_FLUSH();
142                 }
143         
144                 if ((tmp_fp = g_fopen(tmp_file, "wb")) == NULL) {
145                         FILE_OP_ERROR(tmp_file, "fopen");
146                         g_warning("can't open temporary file\n");
147                         fclose(mbox_fp);
148                         g_free(tmp_file);
149                         return -1;
150                 }
151                 if (change_file_mode_rw(tmp_fp, tmp_file) < 0) {
152                         FILE_OP_ERROR(tmp_file, "chmod");
153                 }
154
155                 empty_lines = 0;
156                 lines = 0;
157
158                 /* process all lines from mboxrc file */
159                 while (fgets(buf, sizeof(buf), mbox_fp) != NULL) {
160                         int offset;
161
162                         /* eat empty lines */
163                         if (buf[0] == '\n' || buf[0] == '\r') {
164                                 empty_lines++;
165                                 continue;
166                         }
167
168                         /* From separator or quoted From */
169                         offset = 0;
170                         /* detect leading '>' char(s) */
171                         while ((buf[offset] == '>')) {
172                                 offset++;
173                         }
174                         if (!strncmp(buf+offset, "From ", 5)) {
175                                 /* From separator: */
176                                 if (offset == 0) {
177                                         /* expect next mbox item */
178                                         break;
179                                 }
180
181                                 /* quoted From: */
182                                 /* flush any eaten empty line */
183                                 if (empty_lines > 0) {
184                                         while (empty_lines-- > 0) {
185                                                 FPUTS_TO_TMP_ABORT_IF_FAIL("\n");
186                                 }
187                                         empty_lines = 0;
188                                 }
189                                 /* store the unquoted line */
190                                 FPUTS_TO_TMP_ABORT_IF_FAIL(buf + 1);
191                                 continue;
192                         }
193
194                         /* other line */
195                         /* flush any eaten empty line */
196                         if (empty_lines > 0) {                  
197                                 while (empty_lines-- > 0) {
198                                         FPUTS_TO_TMP_ABORT_IF_FAIL("\n");
199                         }
200                                 empty_lines = 0;
201                         }
202                         /* store the line itself */
203                                         FPUTS_TO_TMP_ABORT_IF_FAIL(buf);
204                 }
205                 /* end of mbox item or end of mbox */
206
207                 /* flush any eaten empty line (but the last one) */
208                 if (empty_lines > 0) {
209                         while (--empty_lines > 0) {
210                                 FPUTS_TO_TMP_ABORT_IF_FAIL("\n");
211                         }
212                 }
213
214                 /* more emails to expect? */
215                 more = !feof(mbox_fp);
216
217                 /* warn if email part is empty (it's the minimum check 
218                    we can do */
219                 if (lines == 0) {
220                         g_warning("malformed mbox: %s: message %d is empty\n", mbox, msgs);
221                         fclose(tmp_fp);
222                         fclose(mbox_fp);
223                         claws_unlink(tmp_file);
224                         return -1;
225                 }
226
227                 if (fclose(tmp_fp) == EOF) {
228                         FILE_OP_ERROR(tmp_file, "fclose");
229                         g_warning("can't write to temporary file\n");
230                         fclose(mbox_fp);
231                         claws_unlink(tmp_file);
232                         g_free(tmp_file);
233                         return -1;
234                 }
235
236                 if (apply_filter) {
237                         if ((msgnum = folder_item_add_msg(dropfolder, tmp_file, NULL, TRUE)) < 0) {
238                                 fclose(mbox_fp);
239                                 claws_unlink(tmp_file);
240                                 g_free(tmp_file);
241                                 return -1;
242                         }
243                         msginfo = folder_item_get_msginfo(dropfolder, msgnum);
244                         to_filter = g_slist_prepend(to_filter, msginfo);
245                 } else {
246                         MsgFileInfo *finfo = g_new0(MsgFileInfo, 1);
247                         finfo->file = tmp_file;
248                         
249                         to_add = g_slist_prepend(to_add, finfo);
250                         tmp_file = get_tmp_file();
251                         
252                         /* flush every 500 */
253                         if (msgs > 0 && msgs % 500 == 0) {
254                                 folder_item_add_msgs(dropfolder, to_add, TRUE);
255                                 procmsg_message_file_list_free(to_add);
256                                 to_add = NULL;
257                         }
258                 }
259                 msgs++;
260         } while (more);
261
262         if (printed)
263                 statusbar_pop_all();
264
265         if (apply_filter) {
266
267                 folder_item_set_batch(dropfolder, FALSE);
268                 procmsg_msglist_filter(to_filter, account, 
269                                 &filtered, &unfiltered, TRUE);
270                 folder_item_set_batch(dropfolder, TRUE);
271
272                 filtering_move_and_copy_msgs(to_filter);
273                 for (cur = filtered; cur; cur = g_slist_next(cur)) {
274                         MsgInfo *info = (MsgInfo *)cur->data;
275                         procmsg_msginfo_free(info);
276                 }
277
278                 unfiltered = g_slist_reverse(unfiltered);
279                 if (unfiltered) {
280                         folder_item_move_msgs(dest, unfiltered);
281                         for (cur = unfiltered; cur; cur = g_slist_next(cur)) {
282                                 MsgInfo *info = (MsgInfo *)cur->data;
283                                 procmsg_msginfo_free(info);
284                         }
285                 }
286
287                 g_slist_free(unfiltered);
288                 g_slist_free(filtered);
289                 g_slist_free(to_filter);
290         } else if (to_add) {
291                 folder_item_add_msgs(dropfolder, to_add, TRUE);
292                 procmsg_message_file_list_free(to_add);
293                 to_add = NULL;
294         }
295
296         folder_item_update_thaw();
297         
298         g_free(tmp_file);
299         fclose(mbox_fp);
300         debug_print("%d messages found.\n", msgs);
301
302         return msgs;
303 }
304
305 gint lock_mbox(const gchar *base, LockType type)
306 {
307 #ifdef G_OS_UNIX
308         gint retval = 0;
309
310         if (type == LOCK_FILE) {
311                 gchar *lockfile, *locklink;
312                 gint retry = 0;
313                 FILE *lockfp;
314
315                 lockfile = g_strdup_printf("%s.%d", base, getpid());
316                 if ((lockfp = g_fopen(lockfile, "wb")) == NULL) {
317                         FILE_OP_ERROR(lockfile, "fopen");
318                         g_warning("can't create lock file %s\n", lockfile);
319                         g_warning("use 'flock' instead of 'file' if possible.\n");
320                         g_free(lockfile);
321                         return -1;
322                 }
323
324                 if (fprintf(lockfp, "%d\n", getpid()) < 0) {
325                         FILE_OP_ERROR(lockfile, "fprintf");
326                         g_free(lockfile);
327                         fclose(lockfp);
328                         return -1;
329                 }
330
331                 if (fclose(lockfp) == EOF) {
332                         FILE_OP_ERROR(lockfile, "fclose");
333                         g_free(lockfile);
334                         return -1;
335                 }
336
337                 locklink = g_strconcat(base, ".lock", NULL);
338                 while (link(lockfile, locklink) < 0) {
339                         FILE_OP_ERROR(lockfile, "link");
340                         if (retry >= 5) {
341                                 g_warning("can't create %s\n", lockfile);
342                                 claws_unlink(lockfile);
343                                 g_free(lockfile);
344                                 return -1;
345                         }
346                         if (retry == 0)
347                                 g_warning("mailbox is owned by another"
348                                             " process, waiting...\n");
349                         retry++;
350                         sleep(5);
351                 }
352                 claws_unlink(lockfile);
353                 g_free(lockfile);
354         } else if (type == LOCK_FLOCK) {
355                 gint lockfd;
356                 gboolean fcntled = FALSE;
357 #if HAVE_FCNTL_H && !defined(G_OS_WIN32)
358                 struct flock fl;
359                 fl.l_type = F_WRLCK;
360                 fl.l_whence = SEEK_SET;
361                 fl.l_start = 0;
362                 fl.l_len = 0;
363 #endif
364
365 #if HAVE_FLOCK
366                 if ((lockfd = g_open(base, O_RDWR, 0)) < 0) {
367 #else
368                 if ((lockfd = g_open(base, O_RDWR, 0)) < 0) {
369 #endif
370                         FILE_OP_ERROR(base, "open");
371                         return -1;
372                 }
373                 
374 #if HAVE_FCNTL_H && !defined(G_OS_WIN32)
375                 if (fcntl(lockfd, F_SETLK, &fl) == -1) {
376                         g_warning("can't fnctl %s (%s)", base, strerror(errno));
377                         return -1;
378                 } else {
379                         fcntled = TRUE;
380                 }
381 #endif
382
383 #if HAVE_FLOCK
384                 if (flock(lockfd, LOCK_EX|LOCK_NB) < 0 && !fcntled) {
385                         perror("flock");
386 #else
387 #if HAVE_LOCKF
388                 if (lockf(lockfd, F_TLOCK, 0) < 0 && !fcntled) {
389                         perror("lockf");
390 #else
391                 {
392 #endif
393 #endif /* HAVE_FLOCK */
394                         g_warning("can't lock %s\n", base);
395                         if (close(lockfd) < 0)
396                                 perror("close");
397                         return -1;
398                 }
399                 retval = lockfd;
400         } else {
401                 g_warning("invalid lock type\n");
402                 return -1;
403         }
404
405         return retval;
406 #else
407         return -1;
408 #endif /* G_OS_UNIX */
409 }
410
411 gint unlock_mbox(const gchar *base, gint fd, LockType type)
412 {
413         if (type == LOCK_FILE) {
414                 gchar *lockfile;
415
416                 lockfile = g_strconcat(base, ".lock", NULL);
417                 if (claws_unlink(lockfile) < 0) {
418                         FILE_OP_ERROR(lockfile, "unlink");
419                         g_free(lockfile);
420                         return -1;
421                 }
422                 g_free(lockfile);
423
424                 return 0;
425         } else if (type == LOCK_FLOCK) {
426                 gboolean fcntled = FALSE;
427 #if HAVE_FCNTL_H && !defined(G_OS_WIN32)
428                 struct flock fl;
429                 fl.l_type = F_UNLCK;
430                 fl.l_whence = SEEK_SET;
431                 fl.l_start = 0;
432                 fl.l_len = 0;
433
434                 if (fcntl(fd, F_SETLK, &fl) == -1) {
435                         g_warning("can't fnctl %s", base);
436                 } else {
437                         fcntled = TRUE;
438                 }
439 #endif
440 #if HAVE_FLOCK
441                 if (flock(fd, LOCK_UN) < 0 && !fcntled) {
442                         perror("flock");
443 #else
444 #if HAVE_LOCKF
445                 if (lockf(fd, F_ULOCK, 0) < 0 && !fcntled) {
446                         perror("lockf");
447 #else
448                 {
449 #endif
450 #endif /* HAVE_FLOCK */
451                         g_warning("can't unlock %s\n", base);
452                         if (close(fd) < 0)
453                                 perror("close");
454                         return -1;
455                 }
456
457                 if (close(fd) < 0) {
458                         perror("close");
459                         return -1;
460                 }
461
462                 return 0;
463         }
464
465         g_warning("invalid lock type\n");
466         return -1;
467 }
468
469 gint copy_mbox(gint srcfd, const gchar *dest)
470 {
471         FILE *dest_fp;
472         ssize_t n_read;
473         gchar buf[BUFSIZ];
474         gboolean err = FALSE;
475         int save_errno = 0;
476
477         if (srcfd < 0) {
478                 return -1;
479         }
480
481         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
482                 FILE_OP_ERROR(dest, "fopen");
483                 return -1;
484         }
485
486         if (change_file_mode_rw(dest_fp, dest) < 0) {
487                 FILE_OP_ERROR(dest, "chmod");
488                 g_warning("can't change file mode\n");
489         }
490
491         while ((n_read = read(srcfd, buf, sizeof(buf))) > 0) {
492                 if (n_read == -1 && errno != 0) {
493                         save_errno = errno;
494                         break;
495                 }
496                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
497                         g_warning("writing to %s failed.\n", dest);
498                         fclose(dest_fp);
499                         claws_unlink(dest);
500                         return -1;
501                 }
502         }
503
504         if (save_errno != 0) {
505                 g_warning("error %d reading mbox: %s\n", save_errno,
506                                 strerror(save_errno));
507                 err = TRUE;
508         }
509
510         if (fclose(dest_fp) == EOF) {
511                 FILE_OP_ERROR(dest, "fclose");
512                 err = TRUE;
513         }
514
515         if (err) {
516                 claws_unlink(dest);
517                 return -1;
518         }
519
520         return 0;
521 }
522
523 void empty_mbox(const gchar *mbox)
524 {
525         FILE *fp;
526
527         if ((fp = g_fopen(mbox, "wb")) == NULL) {
528                 FILE_OP_ERROR(mbox, "fopen");
529                 g_warning("can't truncate mailbox to zero.\n");
530                 return;
531         }
532         fclose(fp);
533 }
534
535 gint export_list_to_mbox(GSList *mlist, const gchar *mbox)
536 /* return values: -2 skipped, -1 error, 0 OK */
537 {
538         GSList *cur;
539         MsgInfo *msginfo;
540         FILE *msg_fp;
541         FILE *mbox_fp;
542         gchar buf[BUFFSIZE];
543         int err = 0;
544
545         gint msgs = 1, total = g_slist_length(mlist);
546         if (g_file_test(mbox, G_FILE_TEST_EXISTS) == TRUE) {
547                 if (alertpanel_full(_("Overwrite mbox file"),
548                                         _("This file already exists. Do you want to overwrite it?"),
549                                         GTK_STOCK_CANCEL, _("Overwrite"), NULL, FALSE,
550                                         NULL, ALERT_WARNING, G_ALERTDEFAULT)
551                                 != G_ALERTALTERNATE) {
552                         return -2;
553                 }
554         }
555
556         if ((mbox_fp = g_fopen(mbox, "wb")) == NULL) {
557                 FILE_OP_ERROR(mbox, "fopen");
558                 alertpanel_error(_("Could not create mbox file:\n%s\n"), mbox);
559                 return -1;
560         }
561
562 #ifdef HAVE_FGETS_UNLOCKED
563         flockfile(mbox_fp);
564 #endif
565
566         statuswindow_print_all(_("Exporting to mbox..."));
567         for (cur = mlist; cur != NULL; cur = cur->next) {
568                 int len;
569                 gchar buft[BUFFSIZE];
570                 msginfo = (MsgInfo *)cur->data;
571
572                 msg_fp = procmsg_open_message(msginfo);
573                 if (!msg_fp) {
574                         continue;
575                 }
576
577 #ifdef HAVE_FGETS_UNLOCKED
578                 flockfile(msg_fp);
579 #endif
580                 strncpy2(buf,
581                          msginfo->from ? msginfo->from :
582                          cur_account && cur_account->address ?
583                          cur_account->address : "unknown",
584                          sizeof(buf));
585                 extract_address(buf);
586
587                 if (fprintf(mbox_fp, "From %s %s",
588                         buf, ctime_r(&msginfo->date_t, buft)) < 0) {
589                         err = -1;
590 #ifdef HAVE_FGETS_UNLOCKED
591                         funlockfile(msg_fp);
592 #endif
593                         fclose(msg_fp);
594                         goto out;
595                 }
596
597                 buf[0] = '\0';
598                 
599                 /* write email to mboxrc */
600                 while (SC_FGETS(buf, sizeof(buf), msg_fp) != NULL) {
601                         /* quote any From, >From, >>From, etc., according to mbox format specs */
602                         int offset;
603
604                         offset = 0;
605                         /* detect leading '>' char(s) */
606                         while ((buf[offset] == '>')) {
607                                 offset++;
608                         }
609                         if (!strncmp(buf+offset, "From ", 5)) {
610                                 if (SC_FPUTC('>', mbox_fp) == EOF) {
611                                         err = -1;
612 #ifdef HAVE_FGETS_UNLOCKED
613                                         funlockfile(msg_fp);
614 #endif
615                                         fclose(msg_fp);
616                                         goto out;
617                                 }
618                         }
619                         if (SC_FPUTS(buf, mbox_fp) == EOF) {
620                                 err = -1;
621 #ifdef HAVE_FGETS_UNLOCKED
622                                 funlockfile(msg_fp);
623 #endif
624                                 fclose(msg_fp);
625                                 goto out;
626                         }
627                 }
628
629                 /* force last line to end w/ a newline */
630                 len = strlen(buf);
631                 if (len > 0) {
632                         len--;
633                         if ((buf[len] != '\n') && (buf[len] != '\r')) {
634                                 if (SC_FPUTC('\n', mbox_fp) == EOF) {
635                                         err = -1;
636 #ifdef HAVE_FGETS_UNLOCKED
637                                         funlockfile(msg_fp);
638 #endif
639                                         fclose(msg_fp);
640                                         goto out;
641                                 }
642                         }
643                 }
644
645                 /* add a trailing empty line */
646                 if (SC_FPUTC('\n', mbox_fp) == EOF) {
647                         err = -1;
648 #ifdef HAVE_FGETS_UNLOCKED
649                         funlockfile(msg_fp);
650 #endif
651                         fclose(msg_fp);
652                         goto out;
653                 }
654
655 #ifdef HAVE_FGETS_UNLOCKED
656                 funlockfile(msg_fp);
657 #endif
658                 fclose(msg_fp);
659                 statusbar_progress_all(msgs++,total, 500);
660                 if (msgs%500 == 0)
661                         GTK_EVENTS_FLUSH();
662         }
663
664 out:
665         statusbar_progress_all(0,0,0);
666         statuswindow_pop_all();
667
668 #ifdef HAVE_FGETS_UNLOCKED
669         funlockfile(mbox_fp);
670 #endif
671         fclose(mbox_fp);
672
673         return err;
674 }
675
676 /* read all messages in SRC, and store them into one MBOX file. */
677 /* return values: -2 skipped, -1 error, 0 OK */
678 gint export_to_mbox(FolderItem *src, const gchar *mbox)
679 {
680         GSList *mlist;
681         gint ret;
682         
683         cm_return_val_if_fail(src != NULL, -1);
684         cm_return_val_if_fail(src->folder != NULL, -1);
685         cm_return_val_if_fail(mbox != NULL, -1);
686
687         debug_print("Exporting messages from %s into %s...\n",
688                     src->path, mbox);
689
690         mlist = folder_item_get_msg_list(src);
691
692         folder_item_update_freeze();
693         ret = export_list_to_mbox(mlist, mbox);
694         folder_item_update_thaw();
695
696         procmsg_msg_list_free(mlist);
697
698         return ret;
699 }