1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Maildir folder support. FIXME rewrite - why do we chdir(2)??
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2014 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
9 * Gunnar Ritter. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by Gunnar Ritter
22 * and his contributors.
23 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 #ifndef HAVE_AMALGAMATION
47 struct message
*md_data
;
51 static struct mditem
*_maildir_table
;
52 static long _maildir_prime
;
53 static sigjmp_buf _maildir_jmp
;
55 /* Do some cleanup in the tmp/ subdir */
56 static void _cleantmp(void);
58 static int maildir_setfile1(const char *name
, int nmail
, int omsgCount
);
59 static int mdcmp(const void *a
, const void *b
);
60 static int subdir(const char *name
, const char *sub
, int nmail
);
61 static void _maildir_append(const char *name
, const char *sub
, const char *fn
);
62 static void readin(const char *name
, struct message
*m
);
63 static void maildir_update(void);
64 static void move(struct message
*m
);
65 static char *mkname(time_t t
, enum mflag f
, const char *pref
);
66 static void maildircatch(int s
);
67 static enum okay
maildir_append1(const char *name
, FILE *fp
, off_t off1
,
68 long size
, enum mflag flag
);
69 static enum okay
trycreate(const char *name
);
70 static enum okay
mkmaildir(const char *name
);
71 static struct message
*mdlook(const char *name
, struct message
*data
);
72 static void mktable(void);
73 static enum okay
subdir_remove(const char *name
, const char *sub
);
84 if ((dirp
= opendir("tmp")) == NULL
)
88 while ((dp
= readdir(dirp
)) != NULL
) {
89 if (dp
->d_name
[0] == '.')
91 sstpcpy(sstpcpy(dep
, "tmp/"), dp
->d_name
);
92 if (stat(dep
, &st
) < 0)
94 if (st
.st_atime
+ 36*3600 < now
)
102 maildir_setfile(char const * volatile name
, int nmail
, int isedit
)
104 sighandler_type
volatile saveint
;
106 int i
= -1, omsgCount
;
108 /* TODO ince we have a VOID box... */
109 omsgCount
= msgCount
;
110 if (cwget(&cw
) == STOP
) {
111 alert("Cannot open current directory");
118 saveint
= safe_signal(SIGINT
, SIG_IGN
);
121 edit
= (isedit
!= 0);
131 mb
.mb_type
= MB_MAILDIR
;
134 if (chdir(name
) < 0) {
135 fprintf(stderr
, "Cannot change directory to \"%s\".\n", name
);/*TODO tr*/
136 mb
.mb_type
= MB_VOID
;
140 safe_signal(SIGINT
, saveint
);
144 _maildir_table
= NULL
;
145 if (sigsetjmp(_maildir_jmp
, 1) == 0) {
148 if (saveint
!= SIG_IGN
)
149 safe_signal(SIGINT
, &maildircatch
);
150 i
= maildir_setfile1(name
, nmail
, omsgCount
);
152 if (nmail
&& _maildir_table
!= NULL
)
153 free(_maildir_table
);
155 safe_signal(SIGINT
, saveint
);
158 mb
.mb_type
= MB_VOID
;
163 if (cwret(&cw
) == STOP
)
164 panic("Cannot change back to current directory.");/* TODO tr */
168 if (nmail
&& mb
.mb_sorted
&& msgCount
> omsgCount
) {
174 if (!nmail
&& !edit
&& msgCount
== 0) {
175 if (mb
.mb_type
== MB_MAILDIR
/* XXX ?? */ && !ok_blook(emptystart
))
176 fprintf(stderr
, tr(258, "No mail at %s\n"), name
);
180 if (nmail
&& msgCount
> omsgCount
)
181 newmailinfo(omsgCount
);
188 maildir_setfile1(const char *name
, int nmail
, int omsgCount
)
194 mb
.mb_perm
= (options
& OPT_R_FLAG
) ? 0 : MB_DELE
;
195 if ((i
= subdir(name
, "cur", nmail
)) != 0)
197 if ((i
= subdir(name
, "new", nmail
)) != 0)
199 _maildir_append(name
, NULL
, NULL
);
200 for (i
= nmail
?omsgCount
:0; i
< msgCount
; i
++)
201 readin(name
, &message
[i
]);
203 if (msgCount
> omsgCount
)
204 qsort(&message
[omsgCount
],
205 msgCount
- omsgCount
,
206 sizeof *message
, mdcmp
);
209 qsort(message
, msgCount
, sizeof *message
, mdcmp
);
215 * In combination with the names from mkname(), this comparison function
216 * ensures that the order of messages in a maildir folder created by mailx
217 * remains always the same. In effect, if a mbox folder is transferred to
218 * a maildir folder by 'copy *', the order of the messages in mailx will
222 mdcmp(const void *a
, const void *b
)
226 if ((i
= ((struct message
const*)a
)->m_time
-
227 ((struct message
const*)b
)->m_time
) == 0)
228 i
= strcmp(&((struct message
const*)a
)->m_maildir_file
[4],
229 &((struct message
const*)b
)->m_maildir_file
[4]);
234 subdir(const char *name
, const char *sub
, int nmail
)
239 if ((dirp
= opendir(sub
)) == NULL
) {
240 fprintf(stderr
, "Cannot open directory \"%s/%s\".\n",
244 if (access(sub
, W_OK
) < 0)
246 while ((dp
= readdir(dirp
)) != NULL
) {
247 if (dp
->d_name
[0] == '.' &&
248 (dp
->d_name
[1] == '\0' ||
249 (dp
->d_name
[1] == '.' &&
250 dp
->d_name
[2] == '\0')))
252 if (dp
->d_name
[0] == '.')
254 if (!nmail
|| mdlook(dp
->d_name
, NULL
) == NULL
)
255 _maildir_append(name
, sub
, dp
->d_name
);
262 _maildir_append(const char *name
, const char *sub
, const char *fn
)
267 enum mflag f
= MUSED
|MNOFROM
|MNEWEST
;
273 if (strcmp(sub
, "new") == 0)
275 t
= strtol(fn
, &xp
, 10);
276 if ((cp
= strrchr(xp
, ',')) != NULL
&&
277 cp
> &xp
[2] && cp
[-1] == '2' && cp
[-2] == ':') {
299 if (msgCount
+ 1 >= msgspace
) {
300 const int chunk
= 64;
301 message
= srealloc(message
,
302 (msgspace
+= chunk
) * sizeof *message
);
303 memset(&message
[msgCount
], 0, chunk
* sizeof *message
);
305 if (fn
== NULL
|| sub
== NULL
)
307 m
= &message
[msgCount
++];
309 m
->m_maildir_file
= smalloc((sz
= strlen(sub
)) + i
+ 2);
310 memcpy(m
->m_maildir_file
, sub
, sz
);
311 m
->m_maildir_file
[sz
] = '/';
312 memcpy(m
->m_maildir_file
+ sz
+ 1, fn
, i
+ 1);
315 m
->m_maildir_hash
= ~pjw(fn
);
320 readin(const char *name
, struct message
*m
)
323 size_t bufsize
, buflen
, cnt
;
324 long size
= 0, lines
= 0;
329 if ((fp
= Fopen(m
->m_maildir_file
, "r")) == NULL
) {
330 fprintf(stderr
, "Cannot read \"%s/%s\" for message %d\n",
331 name
, m
->m_maildir_file
,
332 (int)(m
- &message
[0] + 1));
333 m
->m_flag
|= MHIDDEN
;
336 buf
= smalloc(bufsize
= LINESIZE
);
339 fseek(mb
.mb_otf
, 0L, SEEK_END
);
340 offset
= ftell(mb
.mb_otf
);
341 while (fgetline(&buf
, &bufsize
, &cnt
, &buflen
, fp
, 1) != NULL
) {
343 * Since we simply copy over data without doing any transfer
344 * encoding reclassification/adjustment we *have* to perform
345 * RFC 4155 compliant From_ quoting here
347 if (is_head(buf
, buflen
)) {
348 putc('>', mb
.mb_otf
);
351 size
+= fwrite(buf
, 1, buflen
, mb
.mb_otf
);/*XXX err hdling*/
352 emptyline
= (*buf
== '\n');
356 putc('\n', mb
.mb_otf
);
362 m
->m_size
= m
->m_xsize
= size
;
363 m
->m_lines
= m
->m_xlines
= lines
;
364 m
->m_block
= mailx_blockof(offset
);
365 m
->m_offset
= mailx_offsetof(offset
);
373 sighandler_type saveint
;
377 if (cwget(&cw
) == STOP
) {
378 fprintf(stderr
, "Fatal: Cannot open current directory\n");
381 saveint
= safe_signal(SIGINT
, SIG_IGN
);
382 if (chdir(mailname
) < 0) {
383 fprintf(stderr
, "Cannot change directory to \"%s\".\n",
388 if (sigsetjmp(_maildir_jmp
, 1) == 0) {
389 if (saveint
!= SIG_IGN
)
390 safe_signal(SIGINT
, maildircatch
);
393 safe_signal(SIGINT
, saveint
);
394 if (cwret(&cw
) == STOP
) {
395 fputs("Fatal: Cannot change back to current directory.\n",
406 int dodel
, c
, gotcha
= 0, held
= 0, modflags
= 0;
412 for (m
= &message
[0], c
= 0; m
< &message
[msgCount
]; m
++) {
413 if (m
->m_flag
& MBOX
)
417 if (makembox() == STOP
)
420 for (m
= &message
[0], gotcha
=0, held
=0; m
< &message
[msgCount
]; m
++) {
422 dodel
= m
->m_flag
& MDELETED
;
424 dodel
= !((m
->m_flag
&MPRESERVE
) ||
425 (m
->m_flag
&MTOUCH
) == 0);
427 if (unlink(m
->m_maildir_file
) < 0)
428 fprintf(stderr
, "Cannot delete file \"%s/%s\" "
430 mailname
, m
->m_maildir_file
,
431 (int)(m
- &message
[0] + 1));
435 if ((m
->m_flag
&(MREAD
|MSTATUS
)) == (MREAD
|MSTATUS
) ||
436 m
->m_flag
& (MNEW
|MBOXED
|MSAVED
|MSTATUS
|
447 if ((gotcha
|| modflags
) && edit
) {
448 printf(tr(168, "\"%s\" "), displayname
);
449 printf((ok_blook(bsdcompat
) || ok_blook(bsdmsgs
))
450 ? tr(170, "complete\n") : tr(212, "updated.\n"));
451 } else if (held
&& !edit
&& mb
.mb_perm
!= 0) {
453 printf(tr(155, "Held 1 message in %s\n"), displayname
);
455 printf(tr(156, "Held %d messages in %s\n"), held
,
459 free
: for (m
= &message
[0]; m
< &message
[msgCount
]; m
++)
460 free(m
->m_maildir_file
);
464 move(struct message
*m
)
468 fn
= mkname(0, m
->m_flag
, &m
->m_maildir_file
[4]);
469 new = savecat("cur/", fn
);
470 if (strcmp(m
->m_maildir_file
, new) == 0)
472 if (link(m
->m_maildir_file
, new) < 0) {
473 fprintf(stderr
, "Cannot link \"%s/%s\" to \"%s/%s\": "
474 "message %d not touched.\n",
475 mailname
, m
->m_maildir_file
,
477 (int)(m
- &message
[0] + 1));
480 if (unlink(m
->m_maildir_file
) < 0)
481 fprintf(stderr
, "Cannot unlink \"%s/%s\".\n",
482 mailname
, m
->m_maildir_file
);
486 mkname(time_t t
, enum mflag f
, const char *pref
)
488 static unsigned long cnt
;
501 if (UICMP(32, n
, <, size
+ 8))
502 node
= srealloc(node
, size
+= 20);
505 node
[n
++] = '\\', node
[n
++] = '0',
506 node
[n
++] = '5', node
[n
++] = '7';
509 node
[n
++] = '\\', node
[n
++] = '0',
510 node
[n
++] = '7', node
[n
++] = '2';
517 size
= 60 + strlen(node
);
519 n
= snprintf(cp
, size
, "%lu.%06lu_%06lu.%s:2,",
521 (unsigned long)mypid
, ++cnt
, node
);
523 size
= (n
= strlen(pref
)) + 13;
525 memcpy(cp
, pref
, n
+ 1);
526 for (i
= n
; i
> 3; i
--)
527 if (cp
[i
-1] == ',' && cp
[i
-2] == '2' &&
533 memcpy(cp
+ n
, ":2,", 4);
556 siglongjmp(_maildir_jmp
, s
);
560 maildir_append(const char *name
, FILE *fp
)
563 size_t bufsize
, buflen
, cnt
;
564 off_t off1
= -1, offs
;
566 int flag
= MNEW
|MNEWEST
;
570 if (mkmaildir(name
) != OKAY
)
572 buf
= smalloc(bufsize
= LINESIZE
);
577 bp
= fgetline(&buf
, &bufsize
, &cnt
, &buflen
, fp
, 1);
578 if (bp
== NULL
|| strncmp(buf
, "From ", 5) == 0) {
579 if (off1
!= (off_t
)-1) {
580 ok
= maildir_append1(name
, fp
, off1
,
584 if (fseek(fp
, offs
+buflen
, SEEK_SET
) < 0)
587 off1
= offs
+ buflen
;
594 if (bp
&& buf
[0] == '\n')
596 else if (bp
&& inhead
&& ascncasecmp(buf
, "status", 6) == 0) {
598 while (whitechar(*lp
&0377))
601 while (*++lp
!= '\0')
610 } else if (bp
&& inhead
&&
611 ascncasecmp(buf
, "x-status", 8) == 0) {
613 while (whitechar(*lp
&0377))
616 while (*++lp
!= '\0')
629 } while (bp
!= NULL
);
635 maildir_append1(const char *name
, FILE *fp
, off_t off1
, long size
,
638 int const attempts
= 43200;
639 char buf
[4096], *fn
, *tmp
, *new;
646 /* Create a unique temporary file */
647 for (i
= 0;; sleep(1), ++i
) {
649 fprintf(stderr
, tr(198,
650 "Can't create an unique file name in "
651 "\"%s/tmp\".\n"), name
);
656 fn
= mkname(now
, flag
, NULL
);
657 tmp
= salloc(n
= strlen(name
) + strlen(fn
) + 6);
658 snprintf(tmp
, n
, "%s/tmp/%s", name
, fn
);
659 if (stat(tmp
, &st
) >= 0 || errno
!= ENOENT
)
662 /* Use "wx" for O_EXCL */
663 if ((op
= Fopen(tmp
, "wx")) != NULL
)
667 if (fseek(fp
, off1
, SEEK_SET
) < 0)
670 z
= size
> (long)sizeof buf
? (long)sizeof buf
: size
;
671 if ((n
= fread(buf
, 1, z
, fp
)) != z
||
672 (size_t)n
!= fwrite(buf
, 1, n
, op
)) {
674 fprintf(stderr
, "Error writing to \"%s\".\n", tmp
);
683 new = salloc(n
= strlen(name
) + strlen(fn
) + 6);
684 snprintf(new, n
, "%s/new/%s", name
, fn
);
685 if (link(tmp
, new) < 0) {
686 fprintf(stderr
, "Cannot link \"%s\" to \"%s\".\n", tmp
, new);
690 fprintf(stderr
, "Cannot unlink \"%s\".\n", tmp
);
695 trycreate(const char *name
)
699 if (stat(name
, &st
) == 0) {
700 if (!S_ISDIR(st
.st_mode
)) {
701 fprintf(stderr
, "\"%s\" is not a directory.\n", name
);
704 } else if (makedir(name
) != OKAY
) {
705 fprintf(stderr
, "Cannot create directory \"%s\".\n", name
);
708 imap_created_mailbox
++;
713 mkmaildir(const char *name
)
719 if (trycreate(name
) == OKAY
) {
720 np
= ac_alloc((sz
= strlen(name
)) + 5);
721 memcpy(np
, name
, sz
);
722 memcpy(np
+ sz
, "/tmp", 5);
723 if (trycreate(np
) == OKAY
) {
724 strcpy(&np
[sz
], "/new");
725 if (trycreate(np
) == OKAY
) {
726 strcpy(&np
[sz
], "/cur");
727 if (trycreate(np
) == OKAY
)
736 static struct message
*
737 mdlook(const char *name
, struct message
*data
)
740 unsigned c
, h
, n
= 0;
742 if (data
&& data
->m_maildir_hash
)
743 h
= ~data
->m_maildir_hash
;
747 md
= &_maildir_table
[c
= h
];
748 while (md
->md_data
!= NULL
) {
749 if (strcmp(&md
->md_data
->m_maildir_file
[4], name
) == 0)
751 c
+= n
&1 ? -((n
+1)/2) * ((n
+1)/2) : ((n
+1)/2) * ((n
+1)/2);
753 while (c
>= (unsigned)_maildir_prime
)
754 c
-= (unsigned)_maildir_prime
;
755 md
= &_maildir_table
[c
];
757 if (data
!= NULL
&& md
->md_data
== NULL
)
759 return md
->md_data
? md
->md_data
: NULL
;
767 _maildir_prime
= nextprime(msgCount
);
768 _maildir_table
= scalloc(_maildir_prime
, sizeof *_maildir_table
);
769 for (i
= 0; i
< msgCount
; i
++)
770 mdlook(&message
[i
].m_maildir_file
[4], &message
[i
]);
774 subdir_remove(const char *name
, const char *sub
)
777 int pathsize
, pathend
, namelen
, sublen
, n
;
781 namelen
= strlen(name
);
782 sublen
= strlen(sub
);
783 path
= smalloc(pathsize
= namelen
+ sublen
+ 30);
784 memcpy(path
, name
, namelen
);
786 memcpy(path
+ namelen
+ 1, sub
, sublen
);
787 path
[namelen
+sublen
+1] = '/';
788 path
[pathend
= namelen
+ sublen
+ 2] = '\0';
789 if ((dirp
= opendir(path
)) == NULL
) {
794 while ((dp
= readdir(dirp
)) != NULL
) {
795 if (dp
->d_name
[0] == '.' &&
796 (dp
->d_name
[1] == '\0' ||
797 (dp
->d_name
[1] == '.' &&
798 dp
->d_name
[2] == '\0')))
800 if (dp
->d_name
[0] == '.')
802 n
= strlen(dp
->d_name
);
803 if (UICMP(32, pathend
+ n
+ 1, >, pathsize
))
804 path
= srealloc(path
, pathsize
= pathend
+ n
+ 30);
805 memcpy(path
+ pathend
, dp
->d_name
, n
+ 1);
806 if (unlink(path
) < 0) {
814 path
[pathend
] = '\0';
815 if (rmdir(path
) < 0) {
825 maildir_remove(const char *name
)
827 if (subdir_remove(name
, "tmp") == STOP
||
828 subdir_remove(name
, "new") == STOP
||
829 subdir_remove(name
, "cur") == STOP
)
831 if (rmdir(name
) < 0) {