2 * Heirloom mailx - 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
42 static char sccsid
[] = "@(#)cache.c 1.61 (gritter) 3/4/06";
49 typedef int avoid_empty_file_compiler_warning
;
64 * Mail -- a mail program
69 static char *encname(struct mailbox
*mp
, const char *name
, int same
,
71 static char *encuid(struct mailbox
*mp
, unsigned long uid
);
72 static FILE *clean(struct mailbox
*mp
, struct cw
*cw
);
73 static unsigned long *builds(long *contentelem
);
74 static void purge(struct mailbox
*mp
, struct message
*m
, long mc
,
75 struct cw
*cw
, const char *name
);
76 static int longlt(const void *a
, const void *b
);
77 static void remve(unsigned long n
);
78 static FILE *cache_queue1(struct mailbox
*mp
, char *mode
, char **xname
);
79 static enum okay
dequeue1(struct mailbox
*mp
);
81 static const char infofmt
[] = "%c %lu %d %lu %ld";
84 ((f) & (MSAVED|MDELETED|MREAD|MBOXED|MNEW|MFLAGGED|MANSWERED|MDRAFTED))
86 static const char README1
[] = "\
87 This is a cache directory maintained by mailx(1). You should not change any\n\
88 files within. Nevertheless, the structure is as follows: Each subdirectory\n\
89 of the current directory represents an IMAP account, and each subdirectory\n\
90 below that represents a mailbox. Each mailbox directory contains a file\n\
91 named UIDVALIDITY which describes the validity in relation to the version\n\
92 on the server. Other files have names corresponding to their IMAP UID.\n";
93 static const char README2
[] = "\n\
94 The first 128 bytes of these files are used to store message attributes; the\n\
95 following data is equivalent to compress(1) output. So if you have to save a\n\
96 message by hand because of an emergency, throw away the first 128 bytes and\n\
97 decompress the rest, as e.g. 'dd if=MESSAGEFILE skip=1 bs=128 | zcat' does.\n";
98 static const char README3
[] = "\n\
99 Files named QUEUE contain data that will be sent do the IMAP server next\n\
100 time a connection is made in online mode.\n";
101 static const char README4
[] = "\n\
102 You can safely delete any file or directory here, unless it contains a QUEUE\n\
103 file that is not empty; mailx(1) will download the data again and will also\n\
104 write new cache entries if configured in this way. If you do not wish to use\n\
105 the cache anymore, delete the entire directory and unset the 'imap-cache'\n\
106 variable in mailx(1).\n";
107 static const char README5
[] = "\n\
108 For more information about mailx(1), visit\n\
109 <http://heirloom.sourceforge.net/mailx.html>.\n";
112 encname(struct mailbox
*mp
, const char *name
, int same
, const char *box
)
114 char *cachedir
, *eaccount
, *emailbox
, *ename
, *res
;
117 ename
= strenc(name
);
118 if (mp
->mb_cache_directory
&& same
&& box
== NULL
) {
119 res
= salloc(resz
= strlen(mp
->mb_cache_directory
) +
121 snprintf(res
, resz
, "%s%s%s", mp
->mb_cache_directory
,
122 *ename
? "/" : "", ename
);
124 if ((cachedir
= value("imap-cache")) == NULL
)
126 cachedir
= expand(cachedir
);
127 eaccount
= strenc(mp
->mb_imap_account
);
129 emailbox
= strenc(box
);
130 else if (asccasecmp(mp
->mb_imap_mailbox
, "INBOX"))
131 emailbox
= strenc(mp
->mb_imap_mailbox
);
134 res
= salloc(resz
= strlen(cachedir
) + strlen(eaccount
) +
135 strlen(emailbox
) + strlen(ename
) + 4);
136 snprintf(res
, resz
, "%s/%s/%s%s%s",
137 cachedir
, eaccount
, emailbox
,
138 *ename
? "/" : "", ename
);
144 encuid(struct mailbox
*mp
, unsigned long uid
)
148 snprintf(buf
, sizeof buf
, "%lu", uid
);
149 return encname(mp
, buf
, 1, NULL
);
153 getcache1(struct mailbox
*mp
, struct message
*m
, enum needspec need
,
157 long n
= 0, size
= 0, xsize
, xtime
, xlines
= -1, lines
= 0;
158 int lastc
= EOF
, i
, xflag
, inheader
= 1;
163 if (setflags
== 0 && ((mp
->mb_type
!= MB_IMAP
&&
164 mp
->mb_type
!= MB_CACHE
) ||
167 if ((fp
= Fopen(encuid(mp
, m
->m_uid
), "r")) == NULL
)
169 fcntl_lock(fileno(fp
), F_RDLCK
);
170 if (fscanf(fp
, infofmt
, &b
, (unsigned long*)&xsize
, &xflag
,
171 (unsigned long*)&xtime
, &xlines
) < 4)
173 if (need
!= NEED_UNSPEC
) {
176 if (need
== NEED_HEADER
)
180 if (need
== NEED_HEADER
|| need
== NEED_BODY
)
190 fseek(fp
, INITSKIP
, SEEK_SET
);
192 fseek(mp
->mb_otf
, 0L, SEEK_END
);
193 offset
= ftell(mp
->mb_otf
);
194 while (inheader
&& (n
= zread(zp
, iob
, sizeof iob
)) > 0) {
196 for (i
= 0; i
< n
; i
++) {
197 if (iob
[i
] == '\n') {
204 fwrite(iob
, 1, n
, mp
->mb_otf
);
206 if (n
> 0 && need
== NEED_BODY
) {
207 while ((n
= zread(zp
, iob
, sizeof iob
)) > 0) {
209 for (i
= 0; i
< n
; i
++)
212 fwrite(iob
, 1, n
, mp
->mb_otf
);
216 if (zfree(zp
) < 0 || n
< 0 || ferror(fp
) || ferror(mp
->mb_otf
))
220 m
->m_block
= mailx_blockof(offset
);
221 m
->m_offset
= mailx_offsetof(offset
);
222 flags
: if (setflags
) {
226 m
->m_flag
= xflag
| MNOFROM
;
228 m
->m_flag
|= MHIDDEN
;
231 if (xlines
> 0 && m
->m_xlines
<= 0)
232 m
->m_xlines
= xlines
;
236 if (xflag
== MREAD
&& xlines
> 0)
237 m
->m_flag
|= MFULLYCACHED
;
238 if (need
== NEED_BODY
) {
239 m
->m_have
|= HAVE_HEADER
|HAVE_BODY
;
241 m
->m_xlines
= m
->m_lines
;
246 m
->m_have
|= HAVE_HEADER
;
259 getcache(struct mailbox
*mp
, struct message
*m
, enum needspec need
)
261 return getcache1(mp
, m
, need
, 0);
265 putcache(struct mailbox
*mp
, struct message
*m
)
270 long n
, count
, oldoffset
, osize
, otime
, olines
= -1;
274 if ((mp
->mb_type
!= MB_IMAP
&& mp
->mb_type
!= MB_CACHE
) ||
275 m
->m_uid
== 0 || m
->m_time
== 0 ||
276 (m
->m_flag
& (MTOUCH
|MFULLYCACHED
)) == MFULLYCACHED
)
278 if (m
->m_have
& HAVE_BODY
)
280 else if (m
->m_have
& HAVE_HEADER
)
282 else if (m
->m_have
== HAVE_NOTHING
)
286 oldoffset
= ftell(mp
->mb_itf
);
287 if ((obuf
= Fopen(name
= encuid(mp
, m
->m_uid
), "r+")) == NULL
) {
288 if ((obuf
= Fopen(name
, "w")) == NULL
)
290 fcntl_lock(fileno(obuf
), F_WRLCK
);
292 fcntl_lock(fileno(obuf
), F_WRLCK
);
293 if (fscanf(obuf
, infofmt
, &ob
, (unsigned long*)&osize
, &oflag
,
294 (unsigned long*)&otime
, &olines
) >= 4 &&
295 ob
!= '\0' && (ob
== 'B' ||
296 (ob
== 'H' && c
!= 'B'))) {
297 if (m
->m_xlines
<= 0 && olines
> 0)
298 m
->m_xlines
= olines
;
299 if ((c
!= 'N' && (size_t)osize
!= m
->m_xsize
) ||
300 oflag
!= (int)USEBITS(m
->m_flag
) ||
301 otime
!= m
->m_time
||
303 olines
!= m
->m_xlines
)) {
306 fprintf(obuf
, infofmt
, ob
,
307 (unsigned long)m
->m_xsize
,
309 (unsigned long)m
->m_time
,
318 ftruncate(fileno(obuf
), 0);
320 if ((ibuf
= setinput(mp
, m
, NEED_UNSPEC
)) == NULL
) {
326 fseek(obuf
, INITSKIP
, SEEK_SET
);
330 n
= count
> (long)sizeof iob
? (long)sizeof iob
: count
;
332 if ((size_t)n
!= fread(iob
, 1, n
, ibuf
) ||
333 n
!= (long)zwrite(zp
, iob
, n
)) {
344 fprintf(obuf
, infofmt
, c
, (unsigned long)m
->m_xsize
,
346 (unsigned long)m
->m_time
,
353 if (c
== 'B' && USEBITS(m
->m_flag
) == MREAD
)
354 m
->m_flag
|= MFULLYCACHED
;
355 out
: if (Fclose(obuf
) != 0) {
356 m
->m_flag
&= ~MFULLYCACHED
;
359 fseek(mp
->mb_itf
, oldoffset
, SEEK_SET
);
363 initcache(struct mailbox
*mp
)
370 free(mp
->mb_cache_directory
);
371 mp
->mb_cache_directory
= NULL
;
372 if ((name
= encname(mp
, "", 1, NULL
)) == NULL
)
374 mp
->mb_cache_directory
= sstrdup(name
);
375 if ((uvname
= encname(mp
, "UIDVALIDITY", 1, NULL
)) == NULL
)
377 if (cwget(&cw
) == STOP
)
379 if ((uvfp
= Fopen(uvname
, "r+")) == NULL
||
380 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
381 fscanf(uvfp
, "%lu", &uv
) != 1 ||
382 uv
!= mp
->mb_uidvalidity
) {
383 if ((uvfp
= clean(mp
, &cw
)) == NULL
)
389 fcntl_lock(fileno(uvfp
), F_WRLCK
);
390 fprintf(uvfp
, "%lu\n", mp
->mb_uidvalidity
);
391 if (ferror(uvfp
) || Fclose(uvfp
) != 0) {
393 mp
->mb_uidvalidity
= 0;
399 purgecache(struct mailbox
*mp
, struct message
*m
, long mc
)
404 if ((name
= encname(mp
, "", 1, NULL
)) == NULL
)
406 if (cwget(&cw
) == STOP
)
408 purge(mp
, m
, mc
, &cw
, name
);
413 clean(struct mailbox
*mp
, struct cw
*cw
)
415 char *cachedir
, *eaccount
, *emailbox
, *buf
;
421 if ((cachedir
= value("imap-cache")) == NULL
)
423 cachedir
= expand(cachedir
);
424 eaccount
= strenc(mp
->mb_imap_account
);
425 if (asccasecmp(mp
->mb_imap_mailbox
, "INBOX"))
426 emailbox
= strenc(mp
->mb_imap_mailbox
);
429 buf
= salloc(bufsz
= strlen(cachedir
) + strlen(eaccount
) +
430 strlen(emailbox
) + 40);
431 if (makedir(cachedir
) != OKAY
)
433 snprintf(buf
, bufsz
, "%s/README", cachedir
);
434 if ((fp
= Fopen(buf
, "wx")) != NULL
) {
442 snprintf(buf
, bufsz
, "%s/%s", cachedir
, eaccount
);
443 if (makedir(buf
) != OKAY
)
445 snprintf(buf
, bufsz
, "%s/%s/%s", cachedir
, eaccount
, emailbox
);
446 if (makedir(buf
) != OKAY
)
450 if ((dirfd
= opendir(".")) == NULL
)
452 while ((dp
= readdir(dirfd
)) != NULL
) {
453 if (dp
->d_name
[0] == '.' &&
454 (dp
->d_name
[1] == '\0' ||
455 (dp
->d_name
[1] == '.' &&
456 dp
->d_name
[2] == '\0')))
461 fp
= Fopen("UIDVALIDITY", "w");
462 out
: if (cwret(cw
) == STOP
) {
463 fputs("Fatal: Cannot change back to current directory.\n",
470 static unsigned long *
471 builds(long *contentelem
)
473 unsigned long n
, *contents
= NULL
;
474 long contentalloc
= 0;
480 if ((dirfd
= opendir(".")) == NULL
)
482 while ((dp
= readdir(dirfd
)) != NULL
) {
483 if (dp
->d_name
[0] == '.' &&
484 (dp
->d_name
[1] == '\0' ||
485 (dp
->d_name
[1] == '.' &&
486 dp
->d_name
[2] == '\0')))
488 n
= strtoul(dp
->d_name
, &x
, 10);
491 if (*contentelem
>= contentalloc
- 1)
492 contents
= srealloc(contents
,
493 (contentalloc
+= 200) * sizeof *contents
);
494 contents
[(*contentelem
)++] = n
;
497 if (*contentelem
> 0) {
498 contents
[*contentelem
] = 0;
499 qsort(contents
, *contentelem
, sizeof *contents
, longlt
);
505 purge(struct mailbox
*mp
, struct message
*m
, long mc
, struct cw
*cw
,
508 unsigned long *contents
;
509 long i
, j
, contentelem
;
514 contents
= builds(&contentelem
);
517 while (j
< contentelem
) {
518 if (i
< mc
&& m
[i
].m_uid
== contents
[j
]) {
521 } else if (i
< mc
&& m
[i
].m_uid
< contents
[j
])
524 remve(contents
[j
++]);
527 if (cwret(cw
) == STOP
) {
528 fputs("Fatal: Cannot change back to current directory.\n",
536 longlt(const void *a
, const void *b
)
538 return *(long *)a
- *(long *)b
;
542 remve(unsigned long n
)
546 snprintf(buf
, sizeof buf
, "%lu", n
);
551 delcache(struct mailbox
*mp
, struct message
*m
)
555 fn
= encuid(mp
, m
->m_uid
);
556 if (fn
&& unlink(fn
) == 0)
557 m
->m_flag
|= MUNLINKED
;
561 cache_setptr(int transparent
)
566 unsigned long *contents
;
569 struct message
*omessage
= NULL
;
574 omsgCount
= msgCount
;
576 free(mb
.mb_cache_directory
);
577 mb
.mb_cache_directory
= NULL
;
578 if ((name
= encname(&mb
, "", 1, NULL
)) == NULL
)
580 mb
.mb_cache_directory
= sstrdup(name
);
581 if (cwget(&cw
) == STOP
)
585 contents
= builds(&contentelem
);
586 msgCount
= contentelem
;
587 message
= scalloc(msgCount
+ 1, sizeof *message
);
588 if (cwret(&cw
) == STOP
) {
589 fputs("Fatal: Cannot change back to current directory.\n",
594 for (i
= 0; i
< msgCount
; i
++) {
595 message
[i
].m_uid
= contents
[i
];
596 getcache1(&mb
, &message
[i
], NEED_UNSPEC
, 3);
600 mb
.mb_type
= MB_CACHE
;
601 mb
.mb_perm
= Rflag
? 0 : MB_DELE
;
603 transflags(omessage
, omsgCount
, 1);
611 cache_list(struct mailbox
*mp
, const char *base
, int strip
, FILE *fp
)
613 char *name
, *cachedir
, *eaccount
;
616 const char *cp
, *bp
, *sp
;
619 if ((cachedir
= value("imap-cache")) == NULL
)
621 cachedir
= expand(cachedir
);
622 eaccount
= strenc(mp
->mb_imap_account
);
623 name
= salloc(namesz
= strlen(cachedir
) + strlen(eaccount
) + 2);
624 snprintf(name
, namesz
, "%s/%s", cachedir
, eaccount
);
625 if ((dirfd
= opendir(name
)) == NULL
)
627 while ((dp
= readdir(dirfd
)) != NULL
) {
628 if (dp
->d_name
[0] == '.')
630 cp
= sp
= strdec(dp
->d_name
);
631 for (bp
= base
; *bp
&& *bp
== *sp
; bp
++)
635 cp
= strip
? sp
: cp
;
636 fprintf(fp
, "%s\n", *cp
? cp
: "INBOX");
643 cache_remove(const char *name
)
649 int pathsize
, pathend
, n
;
652 if ((dir
= encname(&mb
, "", 0, protfile(name
))) == NULL
)
654 pathend
= strlen(dir
);
655 path
= smalloc(pathsize
= pathend
+ 30);
657 path
[pathend
++] = '/';
658 path
[pathend
] = '\0';
659 if ((dirfd
= opendir(path
)) == NULL
) {
663 while ((dp
= readdir(dirfd
)) != NULL
) {
664 if (dp
->d_name
[0] == '.' &&
665 (dp
->d_name
[1] == '\0' ||
666 (dp
->d_name
[1] == '.' &&
667 dp
->d_name
[2] == '\0')))
669 n
= strlen(dp
->d_name
);
670 if (pathend
+ n
+ 1 > pathsize
)
671 path
= srealloc(path
, pathsize
= pathend
+ n
+ 30);
672 strcpy(&path
[pathend
], dp
->d_name
);
673 if (stat(path
, &st
) < 0 || (st
.st_mode
&S_IFMT
) != S_IFREG
)
675 if (unlink(path
) < 0) {
683 path
[pathend
] = '\0';
684 rmdir(path
); /* no error on failure, might contain submailboxes */
690 cache_rename(const char *old
, const char *new)
692 char *olddir
, *newdir
;
694 if ((olddir
= encname(&mb
, "", 0, protfile(old
))) == NULL
||
695 (newdir
= encname(&mb
, "", 0, protfile(new))) == NULL
)
697 if (rename(olddir
, newdir
) < 0) {
705 cached_uidvalidity(struct mailbox
*mp
)
711 if ((uvname
= encname(mp
, "UIDVALIDITY", 1, NULL
)) == NULL
)
713 if ((uvfp
= Fopen(uvname
, "r")) == NULL
||
714 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
715 fscanf(uvfp
, "%lu", &uv
) != 1)
722 cache_queue1(struct mailbox
*mp
, char *mode
, char **xname
)
727 if ((name
= encname(mp
, "QUEUE", 0, NULL
)) == NULL
)
729 if ((fp
= Fopen(name
, mode
)) != NULL
)
730 fcntl_lock(fileno(fp
), F_WRLCK
);
737 cache_queue(struct mailbox
*mp
)
741 fp
= cache_queue1(mp
, "a", NULL
);
743 fputs("Cannot queue IMAP command. Retry when online.\n",
749 cache_dequeue(struct mailbox
*mp
)
752 char *cachedir
, *eaccount
, *buf
, *oldbox
;
756 if ((cachedir
= value("imap-cache")) == NULL
)
758 cachedir
= expand(cachedir
);
759 eaccount
= strenc(mp
->mb_imap_account
);
760 buf
= salloc(bufsz
= strlen(cachedir
) + strlen(eaccount
) + 2);
761 snprintf(buf
, bufsz
, "%s/%s", cachedir
, eaccount
);
762 if ((dirfd
= opendir(buf
)) == NULL
)
764 oldbox
= mp
->mb_imap_mailbox
;
765 while ((dp
= readdir(dirfd
)) != NULL
) {
766 if (dp
->d_name
[0] == '.')
768 mp
->mb_imap_mailbox
= strdec(dp
->d_name
);
772 mp
->mb_imap_mailbox
= oldbox
;
777 dequeue1(struct mailbox
*mp
)
779 FILE *fp
= NULL
, *uvfp
= NULL
;
780 char *qname
, *uvname
;
785 fp
= cache_queue1(mp
, "r+", &qname
);
786 if (fp
!= NULL
&& fsize(fp
) > 0) {
787 if (imap_select(mp
, &is_size
, &is_count
,
788 mp
->mb_imap_mailbox
) != OKAY
) {
789 fprintf(stderr
, "Cannot select \"%s\" for dequeuing.\n",
790 mp
->mb_imap_mailbox
);
793 if ((uvname
= encname(mp
, "UIDVALIDITY", 0, NULL
)) == NULL
||
794 (uvfp
= Fopen(uvname
, "r")) == NULL
||
795 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
796 fscanf(uvfp
, "%lu", &uv
) != 1 ||
797 uv
!= mp
->mb_uidvalidity
) {
799 "Unique identifiers for \"%s\" are out of date. "
800 "Cannot commit IMAP commands.\n",
801 mp
->mb_imap_mailbox
);
802 save
: fputs("Saving IMAP commands to dead.letter\n", stderr
);
804 ftruncate(fileno(fp
), 0);
811 printf("Committing IMAP commands for \"%s\"\n",
812 mp
->mb_imap_mailbox
);
813 imap_dequeue(mp
, fp
);
821 #endif /* USE_IMAP */