1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Maildir folder support. FIXME rewrite - why do we chdir(2)??
3 *@ FIXME indeed - my S-Postman Python (!) is faster dealing with maildir!!
5 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
6 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
10 * Gunnar Ritter. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. All advertising materials mentioning features or use of this software
21 * must display the following acknowledgement:
22 * This product includes software developed by Gunnar Ritter
23 * and his contributors.
24 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
25 * may be used to endorse or promote products derived from this software
26 * without specific prior written permission.
28 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
29 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
32 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
41 #define n_FILE maildir
43 #ifndef HAVE_AMALGAMATION
50 struct message
*md_data
;
54 static struct mditem
*_maildir_table
;
55 static ui32_t _maildir_prime
;
56 static sigjmp_buf _maildir_jmp
;
58 static void __maildircatch(int s
);
59 static void __maildircatch_hold(int s
);
61 static unsigned a_maildir_hash(char const *cp
);
63 /* Do some cleanup in the tmp/ subdir */
64 static void _cleantmp(void);
66 static int _maildir_setfile1(char const *name
, enum fedit_mode fm
,
69 /* In combination with the names from mkname(), this comparison function
70 * ensures that the order of messages in a maildir folder created by mailx
71 * remains always the same. In effect, if a mbox folder is transferred to
72 * a maildir folder by 'copy *', the message order wont' change */
73 static int mdcmp(void const *a
, void const *b
);
75 static int _maildir_subdir(char const *name
, char const *sub
,
78 static void _maildir_append(char const *name
, char const *sub
,
81 static void readin(char const *name
, struct message
*m
);
83 static void maildir_update(void);
85 static void _maildir_move(struct message
*m
);
87 static char * mkname(time_t t
, enum mflag f
, char const *pref
);
89 static enum okay
maildir_append1(char const *name
, FILE *fp
, off_t off1
,
90 long size
, enum mflag flag
);
92 static enum okay
trycreate(char const *name
);
94 static enum okay
mkmaildir(char const *name
);
96 static struct message
* mdlook(char const *name
, struct message
*data
);
98 static void mktable(void);
100 static enum okay
subdir_remove(char const *name
, char const *sub
);
103 __maildircatch(int s
)
105 NYD_X
; /* Signal handler */
106 siglongjmp(_maildir_jmp
, s
);
110 __maildircatch_hold(int s
)
112 NYD_X
; /* Signal handler */
114 /* TODO no STDIO in signal handler, no _() tr's -- pre-translate interrupt
116 n_err_sighdl(_("\nImportant operation in progress: "
117 "interrupt again to forcefully abort\n"));
118 safe_signal(SIGINT
, &__maildircatch
);
122 a_maildir_hash(char const *cp
) /* TODO obsolete that -> n_torek_hash */
129 h
= (h
<< 4 & 0xffffffff) + (*cp
&0377);
130 if ((g
= h
& 0xf0000000) != 0) {
149 if ((dirp
= opendir("tmp")) == NULL
)
152 now
= n_time_epoch();
153 while ((dp
= readdir(dirp
)) != NULL
) {
154 if (dp
->d_name
[0] == '.')
156 sstpcpy(sstpcpy(dep
, "tmp/"), dp
->d_name
);
157 if (stat(dep
, &st
) == -1)
159 if (st
.st_atime
+ 36*3600 < now
)
168 _maildir_setfile1(char const *name
, enum fedit_mode fm
, int omsgCount
)
173 if (!(fm
& FEDIT_NEWMAIL
))
176 mb
.mb_perm
= ((n_poption
& n_PO_R_FLAG
) || (fm
& FEDIT_RDONLY
))
178 if ((i
= _maildir_subdir(name
, "cur", fm
)) != 0)
180 if ((i
= _maildir_subdir(name
, "new", fm
)) != 0)
182 _maildir_append(name
, NULL
, NULL
);
185 for (i
= ((fm
& FEDIT_NEWMAIL
) ? omsgCount
: 0); i
< msgCount
; ++i
) {
186 readin(name
, message
+ i
);
191 if (fm
& FEDIT_NEWMAIL
) {
192 if (msgCount
> omsgCount
)
193 qsort(&message
[omsgCount
], msgCount
- omsgCount
, sizeof *message
,
196 qsort(message
, msgCount
, sizeof *message
, &mdcmp
);
204 mdcmp(void const *a
, void const *b
)
206 struct message
const *mpa
= a
, *mpb
= b
;
210 if ((i
= mpa
->m_time
- mpb
->m_time
) == 0)
211 i
= strcmp(mpa
->m_maildir_file
+ 4, mpb
->m_maildir_file
+ 4);
217 _maildir_subdir(char const *name
, char const *sub
, enum fedit_mode fm
)
224 if ((dirp
= opendir(sub
)) == NULL
) {
225 n_err(_("Cannot open directory %s\n"),
226 n_shexp_quote_cp(savecatsep(name
, '/', sub
), FAL0
));
230 if (access(sub
, W_OK
) == -1)
232 while ((dp
= readdir(dirp
)) != NULL
) {
233 if (dp
->d_name
[0] == '.' && (dp
->d_name
[1] == '\0' ||
234 (dp
->d_name
[1] == '.' && dp
->d_name
[2] == '\0')))
236 if (dp
->d_name
[0] == '.')
238 if (!(fm
& FEDIT_NEWMAIL
) || mdlook(dp
->d_name
, NULL
) == NULL
)
239 _maildir_append(name
, sub
, dp
->d_name
);
249 _maildir_append(char const *name
, char const *sub
, char const *fn
)
254 enum mflag f
= MUSED
| MNOFROM
| MNEWEST
;
259 if (fn
!= NULL
&& sub
!= NULL
) {
260 if (!strcmp(sub
, "new"))
266 (void)/*TODO*/n_idec_si64_cp(&tib
, fn
, 10, &xp
);
270 if ((cp
= strrchr(xp
, ',')) != NULL
&& PTRCMP(cp
, >, xp
+ 2) &&
271 cp
[-1] == '2' && cp
[-2] == ':') {
272 while (*++cp
!= '\0') {
294 /* Ensure room (and a NULLified last entry) */
296 message_append(NULL
);
299 if (fn
== NULL
|| sub
== NULL
)
302 m
= message
+ msgCount
++;
304 m
->m_maildir_file
= smalloc((sz
= strlen(sub
)) + i
+ 1 +1);
305 memcpy(m
->m_maildir_file
, sub
, sz
);
306 m
->m_maildir_file
[sz
] = '/';
307 memcpy(m
->m_maildir_file
+ sz
+ 1, fn
, i
+1);
310 m
->m_maildir_hash
= ~a_maildir_hash(fn
);
317 readin(char const *name
, struct message
*m
)
320 size_t bufsize
, buflen
, cnt
;
321 long size
= 0, lines
= 0;
327 if ((fp
= Fopen(m
->m_maildir_file
, "r")) == NULL
) {
328 n_err(_("Cannot read %s for message %lu\n"),
329 n_shexp_quote_cp(savecatsep(name
, '/', m
->m_maildir_file
), FAL0
),
330 (ul_i
)PTR2SIZE(m
- message
+ 1));
331 m
->m_flag
|= MHIDDEN
;
336 fseek(mb
.mb_otf
, 0L, SEEK_END
);
337 offset
= ftell(mb
.mb_otf
);
338 buf
= smalloc(bufsize
= LINESIZE
);
340 while (fgetline(&buf
, &bufsize
, &cnt
, &buflen
, fp
, 1) != NULL
) {
341 /* Since we simply copy over data without doing any transfer
342 * encoding reclassification/adjustment we *have* to perform
343 * RFC 4155 compliant From_ quoting here */
344 if (emptyline
&& is_head(buf
, buflen
, FAL0
)) {
345 putc('>', mb
.mb_otf
);
348 size
+= fwrite(buf
, 1, buflen
, mb
.mb_otf
);/*XXX err hdling*/
349 emptyline
= (*buf
== '\n');
354 putc('\n', mb
.mb_otf
);
361 m
->m_size
= m
->m_xsize
= size
;
362 m
->m_lines
= m
->m_xlines
= lines
;
363 m
->m_block
= mailx_blockof(offset
);
364 m
->m_offset
= mailx_offsetof(offset
);
374 int dodel
, c
, gotcha
= 0, held
= 0, modflags
= 0;
380 if (!(n_pstate
& n_PS_EDIT
)) {
382 for (m
= message
, c
= 0; PTRCMP(m
, <, message
+ msgCount
); ++m
) {
383 if (m
->m_flag
& MBOX
)
387 if (makembox() == STOP
)
390 for (m
= message
, gotcha
= 0, held
= 0; PTRCMP(m
, <, message
+ msgCount
);
392 if (n_pstate
& n_PS_EDIT
)
393 dodel
= m
->m_flag
& MDELETED
;
395 dodel
= !((m
->m_flag
& MPRESERVE
) || !(m
->m_flag
& MTOUCH
));
397 if (unlink(m
->m_maildir_file
) < 0)
398 n_err(_("Cannot delete file %s for message %lu\n"),
399 n_shexp_quote_cp(savecatsep(mailname
, '/', m
->m_maildir_file
),
400 FAL0
), (ul_i
)PTR2SIZE(m
- message
+ 1));
404 if ((m
->m_flag
& (MREAD
| MSTATUS
)) == (MREAD
| MSTATUS
) ||
405 (m
->m_flag
& (MNEW
| MBOXED
| MSAVED
| MSTATUS
| MFLAG
|
406 MUNFLAG
| MANSWER
| MUNANSWER
| MDRAFT
| MUNDRAFT
))) {
414 if ((gotcha
|| modflags
) && (n_pstate
& n_PS_EDIT
)) {
415 fprintf(n_stdout
, "%s %s\n",
416 n_shexp_quote_cp(displayname
, FAL0
),
417 ((ok_blook(bsdcompat
) || ok_blook(bsdmsgs
))
418 ? _("complete") : _("updated.")));
419 } else if (held
&& !(n_pstate
& n_PS_EDIT
) && mb
.mb_perm
!= 0) {
421 fprintf(n_stdout
, _("Held 1 message in %s\n"), displayname
);
423 fprintf(n_stdout
, _("Held %d messages in %s\n"), held
, displayname
);
427 for (m
= message
; PTRCMP(m
, <, message
+ msgCount
); ++m
)
428 free(m
->m_maildir_file
);
433 _maildir_move(struct message
*m
)
438 fn
= mkname(0, m
->m_flag
, m
->m_maildir_file
+ 4);
439 new = savecat("cur/", fn
);
440 if (!strcmp(m
->m_maildir_file
, new))
442 if (link(m
->m_maildir_file
, new) == -1) {
443 n_err(_("Cannot link %s to %s: message %lu not touched\n"),
444 n_shexp_quote_cp(savecatsep(mailname
, '/', m
->m_maildir_file
), FAL0
),
445 n_shexp_quote_cp(savecatsep(mailname
, '/', new), FAL0
),
446 (ul_i
)PTR2SIZE(m
- message
+ 1));
449 if (unlink(m
->m_maildir_file
) == -1)
450 n_err(_("Cannot unlink %s\n"),
451 n_shexp_quote_cp(savecatsep(mailname
, '/', m
->m_maildir_file
), FAL0
));
457 mkname(time_t t
, enum mflag f
, char const *pref
)
459 static unsigned long cnt
;
460 static pid_t mypid
; /* XXX This should possibly be global, somehow */
471 cp
= n_nodename(FAL0
);
474 if (UICMP(32, n
, <, size
+ 8))
475 node
= srealloc(node
, size
+= 20);
478 node
[n
++] = '\\', node
[n
++] = '0',
479 node
[n
++] = '5', node
[n
++] = '7';
482 node
[n
++] = '\\', node
[n
++] = '0',
483 node
[n
++] = '7', node
[n
++] = '2';
488 } while (*cp
++ != '\0');
490 size
= 60 + strlen(node
);
492 n
= snprintf(cp
, size
, "%lu.%06lu_%06lu.%s:2,",
493 (ul_i
)t
, (ul_i
)mypid
, ++cnt
, node
);
495 size
= (n
= strlen(pref
)) + 13;
497 memcpy(cp
, pref
, n
+1);
498 for (i
= n
; i
> 3; --i
)
499 if (cp
[i
- 1] == ',' && cp
[i
- 2] == '2' && cp
[i
- 3] == ':') {
504 memcpy(cp
+ n
, ":2,", 3 +1);
526 maildir_append1(char const *name
, FILE *fp
, off_t off1
, long size
,
529 char buf
[4096], *fn
, *tfn
, *nfn
;
533 size_t nlen
, flen
, n
;
539 /* Create a unique temporary file */
540 for (nfn
= (char*)0xA /* XXX no magic */;; n_msleep(500, FAL0
)) {
541 now
= n_time_epoch();
542 flen
= strlen(fn
= mkname(now
, flag
, NULL
));
543 tfn
= salloc(n
= nlen
+ flen
+ 6);
544 snprintf(tfn
, n
, "%s/tmp/%s", name
, fn
);
546 /* Use "wx" for O_EXCL XXX stat(2) rather redundant; coverity:TOCTOU */
547 if ((!stat(tfn
, &st
) || n_err_no
== n_ERR_NOENT
) &&
548 (op
= Fopen(tfn
, "wx")) != NULL
)
551 nfn
= (char*)(PTR2SIZE(nfn
) - 1);
553 n_err(_("Can't create an unique file name in %s\n"),
554 n_shexp_quote_cp(savecat(name
, "/tmp"), FAL0
));
559 if (fseek(fp
, off1
, SEEK_SET
) == -1)
562 size_t z
= UICMP(z
, size
, >, sizeof buf
) ? sizeof buf
: (size_t)size
;
564 if (z
!= (n
= fread(buf
, 1, z
, fp
)) || n
!= fwrite(buf
, 1, n
, op
)) {
566 n_err(_("Error writing to %s\n"), n_shexp_quote_cp(tfn
, FAL0
));
574 nfn
= salloc(n
= nlen
+ flen
+ 6);
575 snprintf(nfn
, n
, "%s/new/%s", name
, fn
);
576 if (link(tfn
, nfn
) == -1) {
577 n_err(_("Cannot link %s to %s\n"), n_shexp_quote_cp(tfn
, FAL0
),
578 n_shexp_quote_cp(nfn
, FAL0
));
583 if (unlink(tfn
) == -1)
584 n_err(_("Cannot unlink %s\n"), n_shexp_quote_cp(tfn
, FAL0
));
591 trycreate(char const *name
)
597 if (!stat(name
, &st
)) {
598 if (!S_ISDIR(st
.st_mode
)) {
599 n_err(_("%s is not a directory\n"), n_shexp_quote_cp(name
, FAL0
));
602 } else if (!n_path_mkdir(name
)) {
603 n_err(_("Cannot create directory %s\n"), n_shexp_quote_cp(name
, FAL0
));
613 mkmaildir(char const *name
) /* TODO proper cleanup on error; use path[] loop */
620 if (trycreate(name
) == OKAY
) {
621 np
= ac_alloc((sz
= strlen(name
)) + 4 +1);
622 memcpy(np
, name
, sz
);
623 memcpy(np
+ sz
, "/tmp", 4 +1);
624 if (trycreate(np
) == OKAY
) {
625 memcpy(np
+ sz
, "/new", 4 +1);
626 if (trycreate(np
) == OKAY
) {
627 memcpy(np
+ sz
, "/cur", 4 +1);
637 static struct message
*
638 mdlook(char const *name
, struct message
*data
)
644 if (data
&& data
->m_maildir_hash
)
645 h
= ~data
->m_maildir_hash
;
647 h
= a_maildir_hash(name
);
650 md
= _maildir_table
+ c
;
652 while (md
->md_data
!= NULL
) {
653 if (!strcmp(md
->md_data
->m_maildir_file
+ 4, name
))
655 c
+= (n
& 1) ? -((n
+1)/2) * ((n
+1)/2) : ((n
+1)/2) * ((n
+1)/2);
657 while (c
>= _maildir_prime
)
659 md
= _maildir_table
+ c
;
661 if (data
!= NULL
&& md
->md_data
== NULL
)
674 _maildir_prime
= n_prime_next(msgCount
);
675 _maildir_table
= scalloc(_maildir_prime
, sizeof *_maildir_table
);
676 for (mp
= message
, i
= msgCount
; i
-- != 0; ++mp
)
677 mdlook(mp
->m_maildir_file
+ 4, mp
);
682 subdir_remove(char const *name
, char const *sub
)
685 int pathsize
, pathend
, namelen
, sublen
, n
;
691 namelen
= strlen(name
);
692 sublen
= strlen(sub
);
693 path
= smalloc(pathsize
= namelen
+ sublen
+ 30 +1);
694 memcpy(path
, name
, namelen
);
696 memcpy(path
+ namelen
+ 1, sub
, sublen
);
697 path
[namelen
+ sublen
+ 1] = '/';
698 path
[pathend
= namelen
+ sublen
+ 2] = '\0';
700 if ((dirp
= opendir(path
)) == NULL
) {
704 while ((dp
= readdir(dirp
)) != NULL
) {
705 if (dp
->d_name
[0] == '.' && (dp
->d_name
[1] == '\0' ||
706 (dp
->d_name
[1] == '.' && dp
->d_name
[2] == '\0')))
708 if (dp
->d_name
[0] == '.')
710 n
= strlen(dp
->d_name
);
711 if (UICMP(32, pathend
+ n
+1, >, pathsize
))
712 path
= srealloc(path
, pathsize
= pathend
+ n
+ 30);
713 memcpy(path
+ pathend
, dp
->d_name
, n
+1);
714 if (unlink(path
) == -1) {
722 path
[pathend
] = '\0';
723 if (rmdir(path
) == -1) {
735 maildir_setfile(char const * volatile name
, enum fedit_mode fm
)
737 sighandler_type
volatile saveint
;
743 omsgCount
= msgCount
;
744 if (cwget(&cw
) == STOP
) {
745 n_alert(_("Cannot open current directory"));
749 if (!(fm
& FEDIT_NEWMAIL
) && !quit(FAL0
))
752 saveint
= safe_signal(SIGINT
, SIG_IGN
);
754 if (!(fm
& FEDIT_NEWMAIL
)) {
755 if (fm
& FEDIT_SYSBOX
)
756 n_pstate
&= ~n_PS_EDIT
;
758 n_pstate
|= n_PS_EDIT
;
768 mb
.mb_type
= MB_MAILDIR
;
771 if (chdir(name
) < 0) {
772 n_err(_("Cannot change directory to %s\n"), n_shexp_quote_cp(name
, FAL0
));
773 mb
.mb_type
= MB_VOID
;
777 safe_signal(SIGINT
, saveint
);
781 _maildir_table
= NULL
;
782 if (sigsetjmp(_maildir_jmp
, 1) == 0) {
783 if (fm
& FEDIT_NEWMAIL
)
785 if (saveint
!= SIG_IGN
)
786 safe_signal(SIGINT
, &__maildircatch
);
787 i
= _maildir_setfile1(name
, fm
, omsgCount
);
789 if ((fm
& FEDIT_NEWMAIL
) && _maildir_table
!= NULL
)
790 free(_maildir_table
);
792 safe_signal(SIGINT
, saveint
);
795 mb
.mb_type
= MB_VOID
;
800 if (cwret(&cw
) == STOP
)
801 n_panic(_("Cannot change back to current directory"));
805 if ((fm
& FEDIT_NEWMAIL
) && mb
.mb_sorted
&& msgCount
> omsgCount
) {
810 if (!(fm
& FEDIT_NEWMAIL
)) {
811 n_pstate
&= ~n_PS_SAW_COMMAND
;
812 n_pstate
|= n_PS_SETFILE_OPENED
;
815 if ((n_poption
& n_PO_EXISTONLY
) && !(n_poption
& n_PO_HEADERLIST
)) {
820 if (!(fm
& FEDIT_NEWMAIL
) && (fm
& FEDIT_SYSBOX
) && msgCount
== 0) {
821 if (mb
.mb_type
== MB_MAILDIR
/* XXX ?? */ && !ok_blook(emptystart
))
822 n_err(_("No mail at %s\n"), n_shexp_quote_cp(name
, FAL0
));
827 if ((fm
& FEDIT_NEWMAIL
) && msgCount
> omsgCount
)
828 newmailinfo(omsgCount
);
836 maildir_quit(bool_t hold_sigs_on
)
838 sighandler_type saveint
;
848 if (cwget(&cw
) == STOP
) {
849 n_alert(_("Cannot open current directory"));
853 saveint
= safe_signal(SIGINT
, SIG_IGN
);
855 if (chdir(mailname
) == -1) {
856 n_err(_("Cannot change directory to %s\n"),
857 n_shexp_quote_cp(mailname
, FAL0
));
859 safe_signal(SIGINT
, saveint
);
863 if (sigsetjmp(_maildir_jmp
, 1) == 0) {
864 if (saveint
!= SIG_IGN
)
865 safe_signal(SIGINT
, &__maildircatch_hold
);
869 safe_signal(SIGINT
, saveint
);
871 if (cwret(&cw
) == STOP
)
872 n_panic(_("Cannot change back to current directory"));
883 maildir_append(char const *name
, FILE *fp
, long offset
)
886 size_t bufsize
, buflen
, cnt
;
887 off_t off1
= -1, offs
;
890 enum {_NONE
= 0, _INHEAD
= 1<<0, _NLSEP
= 1<<1} state
;
894 if ((rv
= mkmaildir(name
)) != OKAY
)
897 buf
= smalloc(bufsize
= LINESIZE
); /* TODO line pool; signals */
900 offs
= offset
/* BSD will move due to O_APPEND! ftell(fp) */;
904 for (flag
= MNEW
, state
= _NLSEP
;;) {
905 bp
= fgetline(&buf
, &bufsize
, &cnt
, &buflen
, fp
, 1);
908 ((state
& (_INHEAD
| _NLSEP
)) == _NLSEP
&&
909 is_head(buf
, buflen
, FAL0
))) {
910 if (off1
!= (off_t
)-1) {
911 if ((rv
= maildir_append1(name
, fp
, off1
, size
, flag
)) == STOP
)
914 if (fseek(fp
, offs
+ buflen
, SEEK_SET
) == -1) {
919 off1
= offs
+ buflen
;
931 if (buf
[0] == '\n') {
934 } else if (state
& _INHEAD
) {
935 if (!ascncasecmp(buf
, "status", 6)) {
937 while (whitechar(*lp
))
940 while (*++lp
!= '\0')
949 } else if (!ascncasecmp(buf
, "x-status", 8)) {
951 while (whitechar(*lp
))
954 while (*++lp
!= '\0')
980 maildir_remove(char const *name
)
985 if (subdir_remove(name
, "tmp") == STOP
||
986 subdir_remove(name
, "new") == STOP
||
987 subdir_remove(name
, "cur") == STOP
)
989 if (rmdir(name
) == -1) {