summaryrefslogtreecommitdiffabout
path: root/libetpan/src/low-level/maildir/maildir.c
Unidiff
Diffstat (limited to 'libetpan/src/low-level/maildir/maildir.c') (more/less context) (ignore whitespace changes)
-rw-r--r--libetpan/src/low-level/maildir/maildir.c798
1 files changed, 798 insertions, 0 deletions
diff --git a/libetpan/src/low-level/maildir/maildir.c b/libetpan/src/low-level/maildir/maildir.c
new file mode 100644
index 0000000..98b9f87
--- a/dev/null
+++ b/libetpan/src/low-level/maildir/maildir.c
@@ -0,0 +1,798 @@
1/*
2 * libEtPan! -- a mail stuff library
3 *
4 * Copyright (C) 2001, 2005 - DINH Viet Hoa
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the libEtPan! project nor the names of its
16 * contributors may be used to endorse or promote products derived
17 * from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32/*
33 * $Id$
34 */
35
36#include "maildir.h"
37
38#include <string.h>
39#include <stdlib.h>
40#include <stdio.h>
41#include <unistd.h>
42#include <sys/types.h>
43#include <dirent.h>
44#include <time.h>
45#include <sys/stat.h>
46#include <sys/mman.h>
47#include <errno.h>
48#include <fcntl.h>
49
50#ifdef LIBETPAN_SYSTEM_BASENAME
51#include <libgen.h>
52#endif
53
54/*
55 We suppose the maildir mailbox remains on one unique filesystem.
56*/
57
58struct maildir * maildir_new(const char * path)
59{
60 struct maildir * md;
61
62 md = malloc(sizeof(* md));
63 if (md == NULL)
64 goto err;
65
66 md->mdir_counter = 0;
67 md->mdir_mtime_new = (time_t) -1;
68 md->mdir_mtime_cur = (time_t) -1;
69
70 md->mdir_pid = getpid();
71 gethostname(md->mdir_hostname, sizeof(md->mdir_hostname));
72 strncpy(md->mdir_path, path, sizeof(md->mdir_path));
73 md->mdir_path[PATH_MAX - 1] = '\0';
74
75 md->mdir_msg_list = carray_new(128);
76 if (md->mdir_msg_list == NULL)
77 goto free;
78
79 md->mdir_msg_hash = chash_new(CHASH_DEFAULTSIZE, CHASH_COPYNONE);
80 if (md->mdir_msg_hash == NULL)
81 goto free_msg_list;
82
83 return md;
84
85 free_msg_list:
86 carray_free(md->mdir_msg_list);
87 free:
88 free(md);
89 err:
90 return NULL;
91}
92
93static void maildir_flush(struct maildir * md, int msg_new);
94
95void maildir_free(struct maildir * md)
96{
97 maildir_flush(md, 0);
98 maildir_flush(md, 1);
99 chash_free(md->mdir_msg_hash);
100 carray_free(md->mdir_msg_list);
101 free(md);
102}
103
104#define MAX_TRY_ALLOC 32
105
106static char * maildir_get_new_message_filename(struct maildir * md,
107 char * tmpfile)
108{
109 char filename[PATH_MAX];
110 char basename[PATH_MAX];
111 int k;
112 time_t now;
113 int got_file;
114 int r;
115
116 got_file = 0;
117 now = time(NULL);
118 k = 0;
119 while (k < MAX_TRY_ALLOC) {
120 snprintf(basename, sizeof(basename), "%lu.%u_%u.%s",
121 (unsigned long) now, md->mdir_pid, md->mdir_counter, md->mdir_hostname);
122 snprintf(filename, sizeof(filename), "%s/tmp/%s",
123 md->mdir_path, basename);
124
125 if (link(tmpfile, filename) == 0) {
126 got_file = 1;
127 unlink(tmpfile);
128 }
129 else if (errno == EXDEV) {
130 unlink(tmpfile);
131 return NULL;
132 }
133 else if (errno == EPERM) {
134 r = rename(tmpfile, filename);
135 if (r < 0) {
136 unlink(tmpfile);
137 return NULL;
138 }
139 got_file = 1;
140 }
141
142 if (got_file) {
143 char * dup_filename;
144
145 dup_filename = strdup(filename);
146 if (dup_filename == NULL) {
147 unlink(filename);
148 return NULL;
149 }
150
151 md->mdir_counter ++;
152
153 return dup_filename;
154 }
155
156 md->mdir_counter ++;
157 k ++;
158 }
159
160 return NULL;
161}
162
163
164static void msg_free(struct maildir_msg * msg)
165{
166 free(msg->msg_uid);
167 free(msg->msg_filename);
168 free(msg);
169}
170
171/*
172 msg_new()
173
174 filename is given without path
175*/
176
177static struct maildir_msg * msg_new(char * filename, int new_msg)
178{
179 struct maildir_msg * msg;
180 char * p;
181 int flags;
182 size_t uid_len;
183 char * begin_uid;
184
185 /* name of file : xxx-xxx_xxx-xxx:2,SRFT */
186
187 msg = malloc(sizeof(* msg));
188 if (msg == NULL)
189 goto err;
190
191 msg->msg_filename = strdup(filename);
192 if (msg->msg_filename == NULL)
193 goto free;
194
195 begin_uid = filename;
196
197 uid_len = strlen(begin_uid);
198
199 flags = 0;
200 p = strstr(filename, ":2,");
201 if (p != NULL) {
202 uid_len = p - begin_uid;
203
204 p += 3;
205
206 /* parse flags */
207 while (* p != '\0') {
208 switch (* p) {
209 case 'S':
210 flags |= MAILDIR_FLAG_SEEN;
211 break;
212 case 'R':
213 flags |= MAILDIR_FLAG_REPLIED;
214 break;
215 case 'F':
216 flags |= MAILDIR_FLAG_FLAGGED;
217 break;
218 case 'T':
219 flags |= MAILDIR_FLAG_TRASHED;
220 break;
221 }
222 p ++;
223 }
224 }
225
226 if (new_msg)
227 flags |= MAILDIR_FLAG_NEW;
228
229 msg->msg_flags = flags;
230
231 msg->msg_uid = malloc(uid_len + 1);
232 if (msg->msg_uid == NULL)
233 goto free_filename;
234
235 strncpy(msg->msg_uid, begin_uid, uid_len);
236 msg->msg_uid[uid_len] = '\0';
237
238 return msg;
239
240 free_filename:
241 free(msg->msg_filename);
242 free:
243 free(msg);
244 err:
245 return NULL;
246}
247
248static void maildir_flush(struct maildir * md, int msg_new)
249{
250 unsigned int i;
251
252 i = 0;
253 while (i < carray_count(md->mdir_msg_list)) {
254 struct maildir_msg * msg;
255 int delete;
256
257 msg = carray_get(md->mdir_msg_list, i);
258
259 if (msg_new) {
260 delete = 0;
261 if ((msg->msg_flags & MAILDIR_FLAG_NEW) != 0)
262 delete = 1;
263 }
264 else {
265 delete = 1;
266 if ((msg->msg_flags & MAILDIR_FLAG_NEW) != 0)
267 delete = 0;
268 }
269
270 if (delete) {
271 chashdatum key;
272
273 key.data = msg->msg_uid;
274 key.len = strlen(msg->msg_uid);
275 chash_delete(md->mdir_msg_hash, &key, NULL);
276
277 carray_delete(md->mdir_msg_list, i);
278 msg_free(msg);
279 }
280 else {
281 i ++;
282 }
283 }
284}
285
286static int add_message(struct maildir * md,
287 char * filename, int is_new)
288{
289 struct maildir_msg * msg;
290 chashdatum key;
291 chashdatum value;
292 unsigned int i;
293 int res;
294 int r;
295
296 msg = msg_new(filename, is_new);
297 if (msg == NULL) {
298 res = MAILDIR_ERROR_MEMORY;
299 goto err;
300 }
301
302 r = carray_add(md->mdir_msg_list, msg, &i);
303 if (r < 0) {
304 res = MAILDIR_ERROR_MEMORY;
305 goto free_msg;
306 }
307
308 key.data = msg->msg_uid;
309 key.len = strlen(msg->msg_uid);
310 value.data = msg;
311 value.len = 0;
312
313 r = chash_set(md->mdir_msg_hash, &key, &value, NULL);
314 if (r < 0) {
315 res = MAILDIR_ERROR_MEMORY;
316 goto delete;
317 }
318
319 return MAILDIR_NO_ERROR;
320
321 delete:
322 carray_delete(md->mdir_msg_list, i);
323 free_msg:
324 msg_free(msg);
325 err:
326 return res;
327}
328
329static int add_directory(struct maildir * md, char * path, int is_new)
330{
331 DIR * d;
332 struct dirent * entry;
333 int res;
334 int r;
335#if 0
336 char filename[PATH_MAX];
337#endif
338
339 d = opendir(path);
340 if (d == NULL) {
341 res = MAILDIR_ERROR_DIRECTORY;
342 goto err;
343 }
344
345 while ((entry = readdir(d)) != NULL) {
346#if 0
347 struct stat stat_info;
348
349 snprintf(filename, sizeof(filename), "%s/%s", path, entry->d_name);
350
351 r = stat(filename, &stat_info);
352 if (r < 0)
353 continue;
354
355 if (S_ISDIR(stat_info.st_mode))
356 continue;
357#endif
358
359 if (entry->d_name[0] == '.')
360 continue;
361
362 r = add_message(md, entry->d_name, is_new);
363 if (r != MAILDIR_NO_ERROR) {
364 /* ignore errors */
365 }
366 }
367
368 closedir(d);
369
370 return MAILDIR_NO_ERROR;
371
372 err:
373 return res;
374}
375
376int maildir_update(struct maildir * md)
377{
378 struct stat stat_info;
379 char path_new[PATH_MAX];
380 char path_cur[PATH_MAX];
381 char path_maildirfolder[PATH_MAX];
382 int r;
383 int res;
384 int changed;
385
386 snprintf(path_new, sizeof(path_new), "%s/new", md->mdir_path);
387 snprintf(path_cur, sizeof(path_cur), "%s/cur", md->mdir_path);
388
389 changed = 0;
390
391 /* did new/ changed ? */
392
393 r = stat(path_new, &stat_info);
394 if (r < 0) {
395 res = MAILDIR_ERROR_DIRECTORY;
396 goto free;
397 }
398
399 if (md->mdir_mtime_new != stat_info.st_mtime) {
400 md->mdir_mtime_new = stat_info.st_mtime;
401 changed = 1;
402 }
403
404 /* did cur/ changed ? */
405
406 r = stat(path_cur, &stat_info);
407 if (r < 0) {
408 res = MAILDIR_ERROR_DIRECTORY;
409 goto free;
410 }
411
412 if (md->mdir_mtime_cur != stat_info.st_mtime) {
413 md->mdir_mtime_cur = stat_info.st_mtime;
414 changed = 1;
415 }
416
417 if (changed) {
418
419 carray_set_size(md->mdir_msg_list, 0);
420 chash_clear(md->mdir_msg_hash);
421
422 maildir_flush(md, 1);
423
424 /* messages in new */
425 r = add_directory(md, path_new, 1);
426 if (r != MAILDIR_NO_ERROR) {
427 res = r;
428 goto free;
429 }
430
431 maildir_flush(md, 0);
432
433 /* messages in cur */
434 r = add_directory(md, path_cur, 0);
435 if (r != MAILDIR_NO_ERROR) {
436 res = r;
437 goto free;
438 }
439 }
440
441 snprintf(path_maildirfolder, sizeof(path_maildirfolder),
442 "%s/maildirfolder", md->mdir_path);
443
444 if (stat(path_maildirfolder, &stat_info) == -1) {
445 int fd;
446
447 fd = creat(path_maildirfolder, S_IRUSR | S_IWUSR);
448 if (fd != -1)
449 close(fd);
450 }
451
452 return MAILDIR_NO_ERROR;
453
454 free:
455 maildir_flush(md, 0);
456 maildir_flush(md, 1);
457 md->mdir_mtime_cur = (time_t) -1;
458 md->mdir_mtime_new = (time_t) -1;
459 return res;
460}
461
462#ifndef LIBETPAN_SYSTEM_BASENAME
463static char * libetpan_basename(char * filename)
464{
465 char * next;
466 char * p;
467
468 p = filename;
469 next = strchr(p, '/');
470
471 while (next != NULL) {
472 p = next;
473 next = strchr(p + 1, '/');
474 }
475
476 if (p == filename)
477 return filename;
478 else
479 return p + 1;
480}
481#else
482#define libetpan_basename(a) basename(a)
483#endif
484
485int maildir_message_add_uid(struct maildir * md,
486 const char * message, size_t size,
487 char * uid, size_t max_uid_len)
488{
489 char path_new[PATH_MAX];
490 char tmpname[PATH_MAX];
491 int fd;
492 int r;
493 char * mapping;
494 char * delivery_tmp_name;
495 char * delivery_tmp_basename;
496 char delivery_new_name[PATH_MAX];
497 char * delivery_new_basename;
498 int res;
499 struct stat stat_info;
500
501 r = maildir_update(md);
502 if (r != MAILDIR_NO_ERROR) {
503 res = r;
504 goto err;
505 }
506
507 /* write to tmp/ with a classic temporary file */
508
509 snprintf(tmpname, sizeof(tmpname), "%s/tmp/etpan-maildir-XXXXXX",
510 md->mdir_path);
511 fd = mkstemp(tmpname);
512 if (fd < 0) {
513 res = MAILDIR_ERROR_FILE;
514 goto err;
515 }
516
517 r = ftruncate(fd, size);
518 if (r < 0) {
519 res = MAILDIR_ERROR_FILE;
520 goto close;
521 }
522
523 mapping = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
524 if (mapping == MAP_FAILED) {
525 res = MAILDIR_ERROR_FILE;
526 goto close;
527 }
528
529 memcpy(mapping, message, size);
530
531 msync(mapping, size, MS_SYNC);
532 munmap(mapping, size);
533
534 close(fd);
535
536 /* write to tmp/ with maildir standard name */
537
538 delivery_tmp_name = maildir_get_new_message_filename(md, tmpname);
539 if (delivery_tmp_name == NULL) {
540 res = MAILDIR_ERROR_FILE;
541 goto unlink;
542 }
543
544 /* write to new/ with maildir standard name */
545
546 strncpy(tmpname, delivery_tmp_name, sizeof(tmpname));
547 tmpname[sizeof(tmpname) - 1] = '\0';
548
549 delivery_tmp_basename = libetpan_basename(tmpname);
550
551 snprintf(delivery_new_name, sizeof(delivery_new_name), "%s/new/%s",
552 md->mdir_path, delivery_tmp_basename);
553
554 r = link(delivery_tmp_name, delivery_new_name);
555 if (r == 0) {
556 unlink(delivery_tmp_name);
557 }
558 else if (errno == EXDEV) {
559 res = MAILDIR_ERROR_FOLDER;
560 goto unlink_tmp;
561 }
562 else if (errno == EPERM) {
563 r = rename(delivery_tmp_name, delivery_new_name);
564 if (r < 0) {
565 res = MAILDIR_ERROR_FILE;
566 goto unlink_tmp;
567 }
568 }
569
570 snprintf(path_new, sizeof(path_new), "%s/new", md->mdir_path);
571 r = stat(path_new, &stat_info);
572 if (r < 0) {
573 unlink(delivery_new_name);
574 res = MAILDIR_ERROR_FILE;
575 goto unlink_tmp;
576 }
577
578 md->mdir_mtime_new = stat_info.st_mtime;
579
580 delivery_new_basename = libetpan_basename(delivery_new_name);
581
582 r = add_message(md, delivery_new_basename, 1);
583 if (r != MAILDIR_NO_ERROR) {
584 unlink(delivery_new_name);
585 res = MAILDIR_ERROR_FILE;
586 goto unlink_tmp;
587 }
588
589 if (uid != NULL)
590 strncpy(uid, delivery_new_basename, max_uid_len);
591
592 free(delivery_tmp_name);
593
594 return MAILDIR_NO_ERROR;
595
596 unlink_tmp:
597 unlink(delivery_tmp_name);
598 free(delivery_tmp_name);
599 goto err;
600 close:
601 close(fd);
602 unlink:
603 unlink(tmpname);
604 err:
605 return res;
606}
607
608int maildir_message_add(struct maildir * md,
609 const char * message, size_t size)
610{
611 return maildir_message_add_uid(md, message, size,
612 NULL, 0);
613}
614
615int maildir_message_add_file_uid(struct maildir * md, int fd,
616 char * uid, size_t max_uid_len)
617{
618 char * message;
619 struct stat buf;
620 int r;
621
622 if (fstat(fd, &buf) == -1)
623 return MAILDIR_ERROR_FILE;
624
625 message = mmap(NULL, buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
626 if (message == MAP_FAILED)
627 return MAILDIR_ERROR_FILE;
628
629 r = maildir_message_add_uid(md, message, buf.st_size, uid, max_uid_len);
630
631 munmap(message, buf.st_size);
632
633 return r;
634}
635
636int maildir_message_add_file(struct maildir * md, int fd)
637{
638 return maildir_message_add_file_uid(md, fd,
639 NULL, 0);
640}
641
642char * maildir_message_get(struct maildir * md, const char * uid)
643{
644 chashdatum key;
645 chashdatum value;
646 char filename[PATH_MAX];
647 char * dup_filename;
648 struct maildir_msg * msg;
649 char * dir;
650 int r;
651
652 key.data = (void *) uid;
653 key.len = strlen(uid);
654 r = chash_get(md->mdir_msg_hash, &key, &value);
655 if (r < 0)
656 return NULL;
657
658 msg = value.data;
659 if ((msg->msg_flags & MAILDIR_FLAG_NEW) != 0)
660 dir = "new";
661 else
662 dir = "cur";
663
664 snprintf(filename, sizeof(filename), "%s/%s/%s",
665 md->mdir_path, dir, msg->msg_filename);
666
667 dup_filename = strdup(filename);
668 if (dup_filename == NULL)
669 return NULL;
670
671 return dup_filename;
672}
673
674int maildir_message_remove(struct maildir * md, const char * uid)
675{
676 chashdatum key;
677 chashdatum value;
678 char filename[PATH_MAX];
679 struct maildir_msg * msg;
680 char * dir;
681 int r;
682 int res;
683
684 key.data = (void *) uid;
685 key.len = strlen(uid);
686 r = chash_get(md->mdir_msg_hash, &key, &value);
687 if (r < 0) {
688 res = MAILDIR_ERROR_NOT_FOUND;
689 goto err;
690 }
691
692 msg = value.data;
693 if ((msg->msg_flags & MAILDIR_FLAG_NEW) != 0)
694 dir = "new";
695 else
696 dir = "cur";
697
698 snprintf(filename, sizeof(filename), "%s/%s/%s",
699 md->mdir_path, dir, msg->msg_filename);
700
701 r = unlink(filename);
702 if (r < 0) {
703 res = MAILDIR_ERROR_FILE;
704 goto err;
705 }
706
707 return MAILDIR_NO_ERROR;
708
709 err:
710 return res;
711}
712
713int maildir_message_change_flags(struct maildir * md,
714 const char * uid, int new_flags)
715{
716 chashdatum key;
717 chashdatum value;
718 char filename[PATH_MAX];
719 struct maildir_msg * msg;
720 char * dir;
721 int r;
722 char new_filename[PATH_MAX];
723 char flag_str[5];
724 size_t i;
725 int res;
726
727 key.data = (void *) uid;
728 key.len = strlen(uid);
729 r = chash_get(md->mdir_msg_hash, &key, &value);
730 if (r < 0) {
731 res = MAILDIR_ERROR_NOT_FOUND;
732 goto err;
733 }
734
735 msg = value.data;
736 if ((msg->msg_flags & MAILDIR_FLAG_NEW) != 0)
737 dir = "new";
738 else
739 dir = "cur";
740
741 snprintf(filename, sizeof(filename), "%s/%s/%s",
742 md->mdir_path, dir, msg->msg_filename);
743
744 if ((new_flags & MAILDIR_FLAG_NEW) != 0)
745 dir = "new";
746 else
747 dir = "cur";
748
749 i = 0;
750 if ((new_flags & MAILDIR_FLAG_SEEN) != 0) {
751 flag_str[i] = 'S';
752 i ++;
753 }
754 if ((new_flags & MAILDIR_FLAG_REPLIED) != 0) {
755 flag_str[i] = 'R';
756 i ++;
757 }
758 if ((new_flags & MAILDIR_FLAG_FLAGGED) != 0) {
759 flag_str[i] = 'F';
760 i ++;
761 }
762 if ((new_flags & MAILDIR_FLAG_TRASHED) != 0) {
763 flag_str[i] = 'T';
764 i ++;
765 }
766 flag_str[i] = 0;
767
768 if (flag_str[0] == '\0')
769 snprintf(new_filename, sizeof(new_filename), "%s/%s/%s",
770 md->mdir_path, dir, msg->msg_uid);
771 else
772 snprintf(new_filename, sizeof(new_filename), "%s/%s/%s:2,%s",
773 md->mdir_path, dir, msg->msg_uid, flag_str);
774
775 if (strcmp(filename, new_filename) == 0)
776 return MAILDIR_NO_ERROR;
777
778 r = link(filename, new_filename);
779 if (r == 0) {
780 unlink(filename);
781 }
782 else if (errno == EXDEV) {
783 res = MAILDIR_ERROR_FOLDER;
784 goto err;
785 }
786 else if (errno == EPERM) {
787 r = rename(filename, new_filename);
788 if (r < 0) {
789 res = MAILDIR_ERROR_FOLDER;
790 goto err;
791 }
792 }
793
794 return MAILDIR_NO_ERROR;
795
796 err:
797 return res;
798}