2 * S-nail - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
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
43 typedef int avoid_empty_file_compiler_warning
;
58 * Mail -- a mail program
63 static char *encname(struct mailbox
*mp
, const char *name
, int same
,
65 static char *encuid(struct mailbox
*mp
, unsigned long uid
);
66 static FILE *clean(struct mailbox
*mp
, struct cw
*cw
);
67 static unsigned long *builds(long *contentelem
);
68 static void purge(struct mailbox
*mp
, struct message
*m
, long mc
,
69 struct cw
*cw
, const char *name
);
70 static int longlt(const void *a
, const void *b
);
71 static void remve(unsigned long n
);
72 static FILE *cache_queue1(struct mailbox
*mp
, char *mode
, char **xname
);
73 static enum okay
dequeue1(struct mailbox
*mp
);
75 static const char infofmt
[] = "%c %lu %d %lu %ld";
78 ((f) & (MSAVED|MDELETED|MREAD|MBOXED|MNEW|MFLAGGED|MANSWERED|MDRAFTED))
80 static const char README1
[] = "\
81 This is a cache directory maintained by mailx(1). You should not change any\n\
82 files within. Nevertheless, the structure is as follows: Each subdirectory\n\
83 of the current directory represents an IMAP account, and each subdirectory\n\
84 below that represents a mailbox. Each mailbox directory contains a file\n\
85 named UIDVALIDITY which describes the validity in relation to the version\n\
86 on the server. Other files have names corresponding to their IMAP UID.\n";
87 static const char README2
[] = "\n\
88 The first 128 bytes of these files are used to store message attributes; the\n\
89 following data is equivalent to compress(1) output. So if you have to save a\n\
90 message by hand because of an emergency, throw away the first 128 bytes and\n\
91 decompress the rest, as e.g. 'dd if=MESSAGEFILE skip=1 bs=128 | zcat' does.\n";
92 static const char README3
[] = "\n\
93 Files named QUEUE contain data that will be sent do the IMAP server next\n\
94 time a connection is made in online mode.\n";
95 static const char README4
[] = "\n\
96 You can safely delete any file or directory here, unless it contains a QUEUE\n\
97 file that is not empty; mailx(1) will download the data again and will also\n\
98 write new cache entries if configured in this way. If you do not wish to use\n\
99 the cache anymore, delete the entire directory and unset the 'imap-cache'\n\
100 variable in mailx(1).\n";
101 static const char README5
[] = "\n\
102 For more information about mailx(1), visit\n\
103 <http://heirloom.sourceforge.net/mailx.html>.\n";
106 encname(struct mailbox
*mp
, const char *name
, int same
, const char *box
)
108 char *cachedir
, *eaccount
, *emailbox
, *ename
, *res
;
111 ename
= strenc(name
);
112 if (mp
->mb_cache_directory
&& same
&& box
== NULL
) {
113 res
= salloc(resz
= strlen(mp
->mb_cache_directory
) +
115 snprintf(res
, resz
, "%s%s%s", mp
->mb_cache_directory
,
116 *ename
? "/" : "", ename
);
118 if ((cachedir
= value("imap-cache")) == NULL
)
120 cachedir
= file_expand(cachedir
);
121 eaccount
= strenc(mp
->mb_imap_account
);
123 emailbox
= strenc(box
);
124 else if (asccasecmp(mp
->mb_imap_mailbox
, "INBOX"))
125 emailbox
= strenc(mp
->mb_imap_mailbox
);
128 res
= salloc(resz
= strlen(cachedir
) + strlen(eaccount
) +
129 strlen(emailbox
) + strlen(ename
) + 4);
130 snprintf(res
, resz
, "%s/%s/%s%s%s",
131 cachedir
, eaccount
, emailbox
,
132 *ename
? "/" : "", ename
);
138 encuid(struct mailbox
*mp
, unsigned long uid
)
142 snprintf(buf
, sizeof buf
, "%lu", uid
);
143 return encname(mp
, buf
, 1, NULL
);
147 getcache1(struct mailbox
*mp
, struct message
*m
, enum needspec need
,
151 long n
= 0, size
= 0, xsize
, xtime
, xlines
= -1, lines
= 0;
152 int lastc
= EOF
, i
, xflag
, inheader
= 1;
157 if (setflags
== 0 && ((mp
->mb_type
!= MB_IMAP
&&
158 mp
->mb_type
!= MB_CACHE
) ||
161 if ((fp
= Fopen(encuid(mp
, m
->m_uid
), "r")) == NULL
)
163 fcntl_lock(fileno(fp
), F_RDLCK
);
164 if (fscanf(fp
, infofmt
, &b
, (unsigned long*)&xsize
, &xflag
,
165 (unsigned long*)&xtime
, &xlines
) < 4)
167 if (need
!= NEED_UNSPEC
) {
170 if (need
== NEED_HEADER
)
174 if (need
== NEED_HEADER
|| need
== NEED_BODY
)
184 fseek(fp
, INITSKIP
, SEEK_SET
);
186 fseek(mp
->mb_otf
, 0L, SEEK_END
);
187 offset
= ftell(mp
->mb_otf
);
188 while (inheader
&& (n
= zread(zp
, iob
, sizeof iob
)) > 0) {
190 for (i
= 0; i
< n
; i
++) {
191 if (iob
[i
] == '\n') {
198 fwrite(iob
, 1, n
, mp
->mb_otf
);
200 if (n
> 0 && need
== NEED_BODY
) {
201 while ((n
= zread(zp
, iob
, sizeof iob
)) > 0) {
203 for (i
= 0; i
< n
; i
++)
206 fwrite(iob
, 1, n
, mp
->mb_otf
);
210 if (zfree(zp
) < 0 || n
< 0 || ferror(fp
) || ferror(mp
->mb_otf
))
214 m
->m_block
= mailx_blockof(offset
);
215 m
->m_offset
= mailx_offsetof(offset
);
216 flags
: if (setflags
) {
220 m
->m_flag
= xflag
| MNOFROM
;
222 m
->m_flag
|= MHIDDEN
;
225 if (xlines
> 0 && m
->m_xlines
<= 0)
226 m
->m_xlines
= xlines
;
230 if (xflag
== MREAD
&& xlines
> 0)
231 m
->m_flag
|= MFULLYCACHED
;
232 if (need
== NEED_BODY
) {
233 m
->m_have
|= HAVE_HEADER
|HAVE_BODY
;
235 m
->m_xlines
= m
->m_lines
;
240 m
->m_have
|= HAVE_HEADER
;
253 getcache(struct mailbox
*mp
, struct message
*m
, enum needspec need
)
255 return getcache1(mp
, m
, need
, 0);
259 putcache(struct mailbox
*mp
, struct message
*m
)
264 long n
, count
, oldoffset
, osize
, otime
, olines
= -1;
268 if ((mp
->mb_type
!= MB_IMAP
&& mp
->mb_type
!= MB_CACHE
) ||
269 m
->m_uid
== 0 || m
->m_time
== 0 ||
270 (m
->m_flag
& (MTOUCH
|MFULLYCACHED
)) == MFULLYCACHED
)
272 if (m
->m_have
& HAVE_BODY
)
274 else if (m
->m_have
& HAVE_HEADER
)
276 else if (m
->m_have
== HAVE_NOTHING
)
280 oldoffset
= ftell(mp
->mb_itf
);
281 if ((obuf
= Fopen(name
= encuid(mp
, m
->m_uid
), "r+")) == NULL
) {
282 if ((obuf
= Fopen(name
, "w")) == NULL
)
284 fcntl_lock(fileno(obuf
), F_WRLCK
);
286 fcntl_lock(fileno(obuf
), F_WRLCK
);
287 if (fscanf(obuf
, infofmt
, &ob
, (unsigned long*)&osize
, &oflag
,
288 (unsigned long*)&otime
, &olines
) >= 4 &&
289 ob
!= '\0' && (ob
== 'B' ||
290 (ob
== 'H' && c
!= 'B'))) {
291 if (m
->m_xlines
<= 0 && olines
> 0)
292 m
->m_xlines
= olines
;
293 if ((c
!= 'N' && (size_t)osize
!= m
->m_xsize
) ||
294 oflag
!= (int)USEBITS(m
->m_flag
) ||
295 otime
!= m
->m_time
||
297 olines
!= m
->m_xlines
)) {
300 fprintf(obuf
, infofmt
, ob
,
301 (unsigned long)m
->m_xsize
,
303 (unsigned long)m
->m_time
,
312 ftruncate(fileno(obuf
), 0);
314 if ((ibuf
= setinput(mp
, m
, NEED_UNSPEC
)) == NULL
) {
320 fseek(obuf
, INITSKIP
, SEEK_SET
);
324 n
= count
> (long)sizeof iob
? (long)sizeof iob
: count
;
326 if ((size_t)n
!= fread(iob
, 1, n
, ibuf
) ||
327 n
!= (long)zwrite(zp
, iob
, n
)) {
338 fprintf(obuf
, infofmt
, c
, (unsigned long)m
->m_xsize
,
340 (unsigned long)m
->m_time
,
347 if (c
== 'B' && USEBITS(m
->m_flag
) == MREAD
)
348 m
->m_flag
|= MFULLYCACHED
;
349 out
: if (Fclose(obuf
) != 0) {
350 m
->m_flag
&= ~MFULLYCACHED
;
353 fseek(mp
->mb_itf
, oldoffset
, SEEK_SET
);
357 initcache(struct mailbox
*mp
)
364 free(mp
->mb_cache_directory
);
365 mp
->mb_cache_directory
= NULL
;
366 if ((name
= encname(mp
, "", 1, NULL
)) == NULL
)
368 mp
->mb_cache_directory
= sstrdup(name
);
369 if ((uvname
= encname(mp
, "UIDVALIDITY", 1, NULL
)) == NULL
)
371 if (cwget(&cw
) == STOP
)
373 if ((uvfp
= Fopen(uvname
, "r+")) == NULL
||
374 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
375 fscanf(uvfp
, "%lu", &uv
) != 1 ||
376 uv
!= mp
->mb_uidvalidity
) {
377 if ((uvfp
= clean(mp
, &cw
)) == NULL
)
383 fcntl_lock(fileno(uvfp
), F_WRLCK
);
384 fprintf(uvfp
, "%lu\n", mp
->mb_uidvalidity
);
385 if (ferror(uvfp
) || Fclose(uvfp
) != 0) {
387 mp
->mb_uidvalidity
= 0;
393 purgecache(struct mailbox
*mp
, struct message
*m
, long mc
)
398 if ((name
= encname(mp
, "", 1, NULL
)) == NULL
)
400 if (cwget(&cw
) == STOP
)
402 purge(mp
, m
, mc
, &cw
, name
);
407 clean(struct mailbox
*mp
, struct cw
*cw
)
409 char *cachedir
, *eaccount
, *emailbox
, *buf
;
415 if ((cachedir
= value("imap-cache")) == NULL
)
417 cachedir
= file_expand(cachedir
);
418 eaccount
= strenc(mp
->mb_imap_account
);
419 if (asccasecmp(mp
->mb_imap_mailbox
, "INBOX"))
420 emailbox
= strenc(mp
->mb_imap_mailbox
);
423 buf
= salloc(bufsz
= strlen(cachedir
) + strlen(eaccount
) +
424 strlen(emailbox
) + 40);
425 if (makedir(cachedir
) != OKAY
)
427 snprintf(buf
, bufsz
, "%s/README", cachedir
);
428 if ((fp
= Fopen(buf
, "wx")) != NULL
) {
436 snprintf(buf
, bufsz
, "%s/%s", cachedir
, eaccount
);
437 if (makedir(buf
) != OKAY
)
439 snprintf(buf
, bufsz
, "%s/%s/%s", cachedir
, eaccount
, emailbox
);
440 if (makedir(buf
) != OKAY
)
444 if ((dirfd
= opendir(".")) == NULL
)
446 while ((dp
= readdir(dirfd
)) != NULL
) {
447 if (dp
->d_name
[0] == '.' &&
448 (dp
->d_name
[1] == '\0' ||
449 (dp
->d_name
[1] == '.' &&
450 dp
->d_name
[2] == '\0')))
455 fp
= Fopen("UIDVALIDITY", "w");
456 out
: if (cwret(cw
) == STOP
) {
457 fputs("Fatal: Cannot change back to current directory.\n",
464 static unsigned long *
465 builds(long *contentelem
)
467 unsigned long n
, *contents
= NULL
;
468 long contentalloc
= 0;
474 if ((dirfd
= opendir(".")) == NULL
)
476 while ((dp
= readdir(dirfd
)) != NULL
) {
477 if (dp
->d_name
[0] == '.' &&
478 (dp
->d_name
[1] == '\0' ||
479 (dp
->d_name
[1] == '.' &&
480 dp
->d_name
[2] == '\0')))
482 n
= strtoul(dp
->d_name
, &x
, 10);
485 if (*contentelem
>= contentalloc
- 1)
486 contents
= srealloc(contents
,
487 (contentalloc
+= 200) * sizeof *contents
);
488 contents
[(*contentelem
)++] = n
;
491 if (*contentelem
> 0) {
492 contents
[*contentelem
] = 0;
493 qsort(contents
, *contentelem
, sizeof *contents
, longlt
);
499 purge(struct mailbox
*mp
, struct message
*m
, long mc
, struct cw
*cw
,
502 unsigned long *contents
;
503 long i
, j
, contentelem
;
508 contents
= builds(&contentelem
);
511 while (j
< contentelem
) {
512 if (i
< mc
&& m
[i
].m_uid
== contents
[j
]) {
515 } else if (i
< mc
&& m
[i
].m_uid
< contents
[j
])
518 remve(contents
[j
++]);
521 if (cwret(cw
) == STOP
) {
522 fputs("Fatal: Cannot change back to current directory.\n",
530 longlt(const void *a
, const void *b
)
532 return *(long *)a
- *(long *)b
;
536 remve(unsigned long n
)
540 snprintf(buf
, sizeof buf
, "%lu", n
);
545 delcache(struct mailbox
*mp
, struct message
*m
)
549 fn
= encuid(mp
, m
->m_uid
);
550 if (fn
&& unlink(fn
) == 0)
551 m
->m_flag
|= MUNLINKED
;
555 cache_setptr(int transparent
)
560 unsigned long *contents
;
563 struct message
*omessage
= NULL
;
568 omsgCount
= msgCount
;
570 free(mb
.mb_cache_directory
);
571 mb
.mb_cache_directory
= NULL
;
572 if ((name
= encname(&mb
, "", 1, NULL
)) == NULL
)
574 mb
.mb_cache_directory
= sstrdup(name
);
575 if (cwget(&cw
) == STOP
)
579 contents
= builds(&contentelem
);
580 msgCount
= contentelem
;
581 message
= scalloc(msgCount
+ 1, sizeof *message
);
582 if (cwret(&cw
) == STOP
) {
583 fputs("Fatal: Cannot change back to current directory.\n",
588 for (i
= 0; i
< msgCount
; i
++) {
589 message
[i
].m_uid
= contents
[i
];
590 getcache1(&mb
, &message
[i
], NEED_UNSPEC
, 3);
594 mb
.mb_type
= MB_CACHE
;
595 mb
.mb_perm
= Rflag
? 0 : MB_DELE
;
597 transflags(omessage
, omsgCount
, 1);
605 cache_list(struct mailbox
*mp
, const char *base
, int strip
, FILE *fp
)
607 char *name
, *cachedir
, *eaccount
;
610 const char *cp
, *bp
, *sp
;
613 if ((cachedir
= value("imap-cache")) == NULL
)
615 cachedir
= file_expand(cachedir
);
616 eaccount
= strenc(mp
->mb_imap_account
);
617 name
= salloc(namesz
= strlen(cachedir
) + strlen(eaccount
) + 2);
618 snprintf(name
, namesz
, "%s/%s", cachedir
, eaccount
);
619 if ((dirfd
= opendir(name
)) == NULL
)
621 while ((dp
= readdir(dirfd
)) != NULL
) {
622 if (dp
->d_name
[0] == '.')
624 cp
= sp
= strdec(dp
->d_name
);
625 for (bp
= base
; *bp
&& *bp
== *sp
; bp
++)
629 cp
= strip
? sp
: cp
;
630 fprintf(fp
, "%s\n", *cp
? cp
: "INBOX");
637 cache_remove(const char *name
)
643 int pathsize
, pathend
, n
;
646 if ((dir
= encname(&mb
, "", 0, protfile(name
))) == NULL
)
648 pathend
= strlen(dir
);
649 path
= smalloc(pathsize
= pathend
+ 30);
651 path
[pathend
++] = '/';
652 path
[pathend
] = '\0';
653 if ((dirfd
= opendir(path
)) == NULL
) {
657 while ((dp
= readdir(dirfd
)) != NULL
) {
658 if (dp
->d_name
[0] == '.' &&
659 (dp
->d_name
[1] == '\0' ||
660 (dp
->d_name
[1] == '.' &&
661 dp
->d_name
[2] == '\0')))
663 n
= strlen(dp
->d_name
);
664 if (pathend
+ n
+ 1 > pathsize
)
665 path
= srealloc(path
, pathsize
= pathend
+ n
+ 30);
666 strcpy(&path
[pathend
], dp
->d_name
);
667 if (stat(path
, &st
) < 0 || (st
.st_mode
&S_IFMT
) != S_IFREG
)
669 if (unlink(path
) < 0) {
677 path
[pathend
] = '\0';
678 rmdir(path
); /* no error on failure, might contain submailboxes */
684 cache_rename(const char *old
, const char *new)
686 char *olddir
, *newdir
;
688 if ((olddir
= encname(&mb
, "", 0, protfile(old
))) == NULL
||
689 (newdir
= encname(&mb
, "", 0, protfile(new))) == NULL
)
691 if (rename(olddir
, newdir
) < 0) {
699 cached_uidvalidity(struct mailbox
*mp
)
705 if ((uvname
= encname(mp
, "UIDVALIDITY", 1, NULL
)) == NULL
)
707 if ((uvfp
= Fopen(uvname
, "r")) == NULL
||
708 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
709 fscanf(uvfp
, "%lu", &uv
) != 1)
716 cache_queue1(struct mailbox
*mp
, char *mode
, char **xname
)
721 if ((name
= encname(mp
, "QUEUE", 0, NULL
)) == NULL
)
723 if ((fp
= Fopen(name
, mode
)) != NULL
)
724 fcntl_lock(fileno(fp
), F_WRLCK
);
731 cache_queue(struct mailbox
*mp
)
735 fp
= cache_queue1(mp
, "a", NULL
);
737 fputs("Cannot queue IMAP command. Retry when online.\n",
743 cache_dequeue(struct mailbox
*mp
)
746 char *cachedir
, *eaccount
, *buf
, *oldbox
;
750 if ((cachedir
= value("imap-cache")) == NULL
)
752 cachedir
= file_expand(cachedir
);
753 eaccount
= strenc(mp
->mb_imap_account
);
754 buf
= salloc(bufsz
= strlen(cachedir
) + strlen(eaccount
) + 2);
755 snprintf(buf
, bufsz
, "%s/%s", cachedir
, eaccount
);
756 if ((dirfd
= opendir(buf
)) == NULL
)
758 oldbox
= mp
->mb_imap_mailbox
;
759 while ((dp
= readdir(dirfd
)) != NULL
) {
760 if (dp
->d_name
[0] == '.')
762 mp
->mb_imap_mailbox
= strdec(dp
->d_name
);
766 mp
->mb_imap_mailbox
= oldbox
;
771 dequeue1(struct mailbox
*mp
)
773 FILE *fp
= NULL
, *uvfp
= NULL
;
774 char *qname
, *uvname
;
779 fp
= cache_queue1(mp
, "r+", &qname
);
780 if (fp
!= NULL
&& fsize(fp
) > 0) {
781 if (imap_select(mp
, &is_size
, &is_count
,
782 mp
->mb_imap_mailbox
) != OKAY
) {
783 fprintf(stderr
, "Cannot select \"%s\" for dequeuing.\n",
784 mp
->mb_imap_mailbox
);
787 if ((uvname
= encname(mp
, "UIDVALIDITY", 0, NULL
)) == NULL
||
788 (uvfp
= Fopen(uvname
, "r")) == NULL
||
789 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
790 fscanf(uvfp
, "%lu", &uv
) != 1 ||
791 uv
!= mp
->mb_uidvalidity
) {
793 "Unique identifiers for \"%s\" are out of date. "
794 "Cannot commit IMAP commands.\n",
795 mp
->mb_imap_mailbox
);
796 save
: fputs("Saving IMAP commands to dead.letter\n", stderr
);
798 ftruncate(fileno(fp
), 0);
805 printf("Committing IMAP commands for \"%s\"\n",
806 mp
->mb_imap_mailbox
);
807 imap_dequeue(mp
, fp
);
815 #endif /* USE_IMAP */