2 * Heirloom mailx - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
8 * Gunnar Ritter. All rights reserved.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by Gunnar Ritter
21 * and his contributors.
22 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
23 * may be used to endorse or promote products derived from this software
24 * without specific prior written permission.
26 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
41 static char sccsid
[] = "@(#)cache.c 1.61 (gritter) 3/4/06";
61 * Mail -- a mail program
66 static char *encname(struct mailbox
*mp
, const char *name
, int same
,
68 static char *encuid(struct mailbox
*mp
, unsigned long uid
);
69 static FILE *clean(struct mailbox
*mp
, struct cw
*cw
);
70 static unsigned long *builds(long *contentelem
);
71 static void purge(struct mailbox
*mp
, struct message
*m
, long mc
,
72 struct cw
*cw
, const char *name
);
73 static int longlt(const void *a
, const void *b
);
74 static void remve(unsigned long n
);
75 static FILE *cache_queue1(struct mailbox
*mp
, char *mode
, char **xname
);
76 static enum okay
dequeue1(struct mailbox
*mp
);
78 static const char infofmt
[] = "%c %lu %u %lu %lu";
81 ((f) & (MSAVED|MDELETED|MREAD|MBOXED|MNEW|MFLAGGED|MANSWERED|MDRAFTED))
83 static const char README1
[] = "\
84 This is a cache directory maintained by mailx(1). You should not change any\n\
85 files within. Nevertheless, the structure is as follows: Each subdirectory\n\
86 of the current directory represents an IMAP account, and each subdirectory\n\
87 below that represents a mailbox. Each mailbox directory contains a file\n\
88 named UIDVALIDITY which describes the validity in relation to the version\n\
89 on the server. Other files have names corresponding to their IMAP UID.\n";
90 static const char README2
[] = "\n\
91 The first 128 bytes of these files are used to store message attributes; the\n\
92 following data is equivalent to compress(1) output. So if you have to save a\n\
93 message by hand because of an emergency, throw away the first 128 bytes and\n\
94 decompress the rest, as e.g. 'dd if=MESSAGEFILE skip=1 bs=128 | zcat' does.\n";
95 static const char README3
[] = "\n\
96 Files named QUEUE contain data that will be sent do the IMAP server next\n\
97 time a connection is made in online mode.\n";
98 static const char README4
[] = "\n\
99 You can safely delete any file or directory here, unless it contains a QUEUE\n\
100 file that is not empty; mailx(1) will download the data again and will also\n\
101 write new cache entries if configured in this way. If you do not wish to use\n\
102 the cache anymore, delete the entire directory and unset the 'imap-cache'\n\
103 variable in mailx(1).\n";
104 static const char README5
[] = "\n\
105 For more information about mailx(1), visit\n\
106 <http://heirloom.sourceforge.net/mailx.html>.\n";
109 encname(struct mailbox
*mp
, const char *name
, int same
, const char *box
)
111 char *cachedir
, *eaccount
, *emailbox
, *ename
, *res
;
114 ename
= strenc(name
);
115 if (mp
->mb_cache_directory
&& same
&& box
== NULL
) {
116 res
= salloc(resz
= strlen(mp
->mb_cache_directory
) +
118 snprintf(res
, resz
, "%s%s%s", mp
->mb_cache_directory
,
119 *ename
? "/" : "", ename
);
121 if ((cachedir
= value("imap-cache")) == NULL
)
123 cachedir
= expand(cachedir
);
124 eaccount
= strenc(mp
->mb_imap_account
);
126 emailbox
= strenc(box
);
127 else if (asccasecmp(mp
->mb_imap_mailbox
, "INBOX"))
128 emailbox
= strenc(mp
->mb_imap_mailbox
);
131 res
= salloc(resz
= strlen(cachedir
) + strlen(eaccount
) +
132 strlen(emailbox
) + strlen(ename
) + 4);
133 snprintf(res
, resz
, "%s/%s/%s%s%s",
134 cachedir
, eaccount
, emailbox
,
135 *ename
? "/" : "", ename
);
141 encuid(struct mailbox
*mp
, unsigned long uid
)
145 snprintf(buf
, sizeof buf
, "%lu", uid
);
146 return encname(mp
, buf
, 1, NULL
);
150 getcache1(struct mailbox
*mp
, struct message
*m
, enum needspec need
,
154 long n
= 0, size
= 0, xsize
, xtime
, xlines
= -1, lines
= 0;
155 int lastc
= EOF
, i
, xflag
, inheader
= 1;
160 if (setflags
== 0 && ((mp
->mb_type
!= MB_IMAP
&&
161 mp
->mb_type
!= MB_CACHE
) ||
164 if ((fp
= Fopen(encuid(mp
, m
->m_uid
), "r")) == NULL
)
166 fcntl_lock(fileno(fp
), F_RDLCK
);
167 if (fscanf(fp
, infofmt
, &b
, &xsize
, &xflag
, &xtime
, &xlines
) < 4)
169 if (need
!= NEED_UNSPEC
) {
172 if (need
== NEED_HEADER
)
176 if (need
== NEED_HEADER
|| need
== NEED_BODY
)
186 fseek(fp
, INITSKIP
, SEEK_SET
);
188 fseek(mp
->mb_otf
, 0L, SEEK_END
);
189 offset
= ftell(mp
->mb_otf
);
190 while (inheader
&& (n
= zread(zp
, iob
, sizeof iob
)) > 0) {
192 for (i
= 0; i
< n
; i
++) {
193 if (iob
[i
] == '\n') {
200 fwrite(iob
, 1, n
, mp
->mb_otf
);
202 if (n
> 0 && need
== NEED_BODY
) {
203 while ((n
= zread(zp
, iob
, sizeof iob
)) > 0) {
205 for (i
= 0; i
< n
; i
++)
208 fwrite(iob
, 1, n
, mp
->mb_otf
);
212 if (zfree(zp
) < 0 || n
< 0 || ferror(fp
) || ferror(mp
->mb_otf
))
216 m
->m_block
= mailx_blockof(offset
);
217 m
->m_offset
= mailx_offsetof(offset
);
218 flags
: if (setflags
) {
222 m
->m_flag
= xflag
| MNOFROM
;
224 m
->m_flag
|= MHIDDEN
;
227 if (xlines
> 0 && m
->m_xlines
<= 0)
228 m
->m_xlines
= xlines
;
232 if (xflag
== MREAD
&& xlines
> 0)
233 m
->m_flag
|= MFULLYCACHED
;
234 if (need
== NEED_BODY
) {
235 m
->m_have
|= HAVE_HEADER
|HAVE_BODY
;
237 m
->m_xlines
= m
->m_lines
;
242 m
->m_have
|= HAVE_HEADER
;
255 getcache(struct mailbox
*mp
, struct message
*m
, enum needspec need
)
257 return getcache1(mp
, m
, need
, 0);
261 putcache(struct mailbox
*mp
, struct message
*m
)
266 long n
, count
, oldoffset
, osize
, otime
, olines
= -1;
270 if ((mp
->mb_type
!= MB_IMAP
&& mp
->mb_type
!= MB_CACHE
) ||
271 m
->m_uid
== 0 || m
->m_time
== 0 ||
272 (m
->m_flag
& (MTOUCH
|MFULLYCACHED
)) == MFULLYCACHED
)
274 if (m
->m_have
& HAVE_BODY
)
276 else if (m
->m_have
& HAVE_HEADER
)
278 else if (m
->m_have
== HAVE_NOTHING
)
282 oldoffset
= ftell(mp
->mb_itf
);
283 if ((obuf
= Fopen(name
= encuid(mp
, m
->m_uid
), "r+")) == NULL
) {
284 if ((obuf
= Fopen(name
, "w")) == NULL
)
286 fcntl_lock(fileno(obuf
), F_WRLCK
);
288 fcntl_lock(fileno(obuf
), F_WRLCK
);
289 if (fscanf(obuf
, infofmt
, &ob
, &osize
, &oflag
, &otime
,
290 &olines
) >= 4 && ob
!= '\0' &&
291 (ob
== 'B' || (ob
== 'H' && c
!= 'B'))) {
292 if (m
->m_xlines
<= 0 && olines
> 0)
293 m
->m_xlines
= olines
;
294 if ((c
!= 'N' && osize
!= m
->m_xsize
) ||
295 oflag
!= USEBITS(m
->m_flag
) ||
296 otime
!= m
->m_time
||
298 olines
!= m
->m_xlines
)) {
301 fprintf(obuf
, infofmt
, ob
,
313 ftruncate(fileno(obuf
), 0);
315 if ((ibuf
= setinput(mp
, m
, NEED_UNSPEC
)) == NULL
) {
321 fseek(obuf
, INITSKIP
, SEEK_SET
);
325 n
= count
> sizeof iob
? sizeof iob
: count
;
327 if (fread(iob
, 1, n
, ibuf
) != n
|| zwrite(zp
, iob
, n
) != n
) {
338 fprintf(obuf
, infofmt
, c
, (long)m
->m_xsize
,
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
= 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
;
507 contents
= builds(&contentelem
);
510 while (j
< contentelem
) {
511 if (i
< mc
&& m
[i
].m_uid
== contents
[j
]) {
514 } else if (i
< mc
&& m
[i
].m_uid
< contents
[j
])
517 remve(contents
[j
++]);
520 if (cwret(cw
) == STOP
) {
521 fputs("Fatal: Cannot change back to current directory.\n",
529 longlt(const void *a
, const void *b
)
531 return *(long *)a
- *(long *)b
;
535 remve(unsigned long n
)
539 snprintf(buf
, sizeof buf
, "%lu", n
);
544 delcache(struct mailbox
*mp
, struct message
*m
)
548 fn
= encuid(mp
, m
->m_uid
);
549 if (fn
&& unlink(fn
) == 0)
550 m
->m_flag
|= MUNLINKED
;
554 cache_setptr(int transparent
)
559 unsigned long *contents
;
562 struct message
*omessage
= NULL
;
567 omsgCount
= msgCount
;
569 free(mb
.mb_cache_directory
);
570 mb
.mb_cache_directory
= NULL
;
571 if ((name
= encname(&mb
, "", 1, NULL
)) == NULL
)
573 mb
.mb_cache_directory
= sstrdup(name
);
574 if (cwget(&cw
) == STOP
)
578 contents
= builds(&contentelem
);
579 msgCount
= contentelem
;
580 message
= scalloc(msgCount
+ 1, sizeof *message
);
581 if (cwret(&cw
) == STOP
) {
582 fputs("Fatal: Cannot change back to current directory.\n",
587 for (i
= 0; i
< msgCount
; i
++) {
588 message
[i
].m_uid
= contents
[i
];
589 getcache1(&mb
, &message
[i
], NEED_UNSPEC
, 3);
593 mb
.mb_type
= MB_CACHE
;
594 mb
.mb_perm
= Rflag
? 0 : MB_DELE
;
596 transflags(omessage
, omsgCount
, 1);
604 cache_list(struct mailbox
*mp
, const char *base
, int strip
, FILE *fp
)
606 char *name
, *cachedir
, *eaccount
;
609 const char *cp
, *bp
, *sp
;
612 if ((cachedir
= value("imap-cache")) == NULL
)
614 cachedir
= expand(cachedir
);
615 eaccount
= strenc(mp
->mb_imap_account
);
616 name
= salloc(namesz
= strlen(cachedir
) + strlen(eaccount
) + 2);
617 snprintf(name
, namesz
, "%s/%s", cachedir
, eaccount
);
618 if ((dirfd
= opendir(name
)) == NULL
)
620 while ((dp
= readdir(dirfd
)) != NULL
) {
621 if (dp
->d_name
[0] == '.')
623 cp
= sp
= strdec(dp
->d_name
);
624 for (bp
= base
; *bp
&& *bp
== *sp
; bp
++)
628 cp
= strip
? sp
: cp
;
629 fprintf(fp
, "%s\n", *cp
? cp
: "INBOX");
636 cache_remove(const char *name
)
642 int pathsize
, pathend
, n
;
645 if ((dir
= encname(&mb
, "", 0, protfile(name
))) == NULL
)
647 pathend
= strlen(dir
);
648 path
= smalloc(pathsize
= pathend
+ 30);
650 path
[pathend
++] = '/';
651 path
[pathend
] = '\0';
652 if ((dirfd
= opendir(path
)) == NULL
) {
656 while ((dp
= readdir(dirfd
)) != NULL
) {
657 if (dp
->d_name
[0] == '.' &&
658 (dp
->d_name
[1] == '\0' ||
659 (dp
->d_name
[1] == '.' &&
660 dp
->d_name
[2] == '\0')))
662 n
= strlen(dp
->d_name
);
663 if (pathend
+ n
+ 1 > pathsize
)
664 path
= srealloc(path
, pathsize
= pathend
+ n
+ 30);
665 strcpy(&path
[pathend
], dp
->d_name
);
666 if (stat(path
, &st
) < 0 || (st
.st_mode
&S_IFMT
) != S_IFREG
)
668 if (unlink(path
) < 0) {
676 path
[pathend
] = '\0';
677 rmdir(path
); /* no error on failure, might contain submailboxes */
683 cache_rename(const char *old
, const char *new)
685 char *olddir
, *newdir
;
687 if ((olddir
= encname(&mb
, "", 0, protfile(old
))) == NULL
||
688 (newdir
= encname(&mb
, "", 0, protfile(new))) == NULL
)
690 if (rename(olddir
, newdir
) < 0) {
698 cached_uidvalidity(struct mailbox
*mp
)
704 if ((uvname
= encname(mp
, "UIDVALIDITY", 1, NULL
)) == NULL
)
706 if ((uvfp
= Fopen(uvname
, "r")) == NULL
||
707 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
708 fscanf(uvfp
, "%lu", &uv
) != 1)
715 cache_queue1(struct mailbox
*mp
, char *mode
, char **xname
)
720 if ((name
= encname(mp
, "QUEUE", 0, NULL
)) == NULL
)
722 if ((fp
= Fopen(name
, mode
)) != NULL
)
723 fcntl_lock(fileno(fp
), F_WRLCK
);
730 cache_queue(struct mailbox
*mp
)
734 fp
= cache_queue1(mp
, "a", NULL
);
736 fputs("Cannot queue IMAP command. Retry when online.\n",
742 cache_dequeue(struct mailbox
*mp
)
745 char *cachedir
, *eaccount
, *buf
, *oldbox
;
749 if ((cachedir
= value("imap-cache")) == NULL
)
751 cachedir
= expand(cachedir
);
752 eaccount
= strenc(mp
->mb_imap_account
);
753 buf
= salloc(bufsz
= strlen(cachedir
) + strlen(eaccount
) + 2);
754 snprintf(buf
, bufsz
, "%s/%s", cachedir
, eaccount
);
755 if ((dirfd
= opendir(buf
)) == NULL
)
757 oldbox
= mp
->mb_imap_mailbox
;
758 while ((dp
= readdir(dirfd
)) != NULL
) {
759 if (dp
->d_name
[0] == '.')
761 mp
->mb_imap_mailbox
= strdec(dp
->d_name
);
765 mp
->mb_imap_mailbox
= oldbox
;
770 dequeue1(struct mailbox
*mp
)
772 FILE *fp
= NULL
, *uvfp
= NULL
;
773 char *qname
, *uvname
;
778 fp
= cache_queue1(mp
, "r+", &qname
);
779 if (fp
!= NULL
&& fsize(fp
) > 0) {
780 if (imap_select(mp
, &is_size
, &is_count
,
781 mp
->mb_imap_mailbox
) != OKAY
) {
782 fprintf(stderr
, "Cannot select \"%s\" for dequeuing.\n",
783 mp
->mb_imap_mailbox
);
786 if ((uvname
= encname(mp
, "UIDVALIDITY", 0, NULL
)) == NULL
||
787 (uvfp
= Fopen(uvname
, "r")) == NULL
||
788 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
789 fscanf(uvfp
, "%lu", &uv
) != 1 ||
790 uv
!= mp
->mb_uidvalidity
) {
792 "Unique identifiers for \"%s\" are out of date. "
793 "Cannot commit IMAP commands.\n",
794 mp
->mb_imap_mailbox
);
795 save
: fputs("Saving IMAP commands to dead.letter\n", stderr
);
797 ftruncate(fileno(fp
), 0);
804 printf("Committing IMAP commands for \"%s\"\n",
805 mp
->mb_imap_mailbox
);
806 imap_dequeue(mp
, fp
);
814 #endif /* HAVE_SOCKETS */