1 /*@ 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 - 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
44 EMPTY_FILE(imap_cache
)
49 static char *encname(struct mailbox
*mp
, const char *name
, int same
,
51 static char *encuid(struct mailbox
*mp
, unsigned long uid
);
52 static FILE *clean(struct mailbox
*mp
, struct cw
*cw
);
53 static unsigned long *builds(long *contentelem
);
54 static void purge(struct mailbox
*mp
, struct message
*m
, long mc
,
55 struct cw
*cw
, const char *name
);
56 static int longlt(const void *a
, const void *b
);
57 static void remve(unsigned long n
);
58 static FILE *cache_queue1(struct mailbox
*mp
, char const *mode
, char **xname
);
59 static enum okay
dequeue1(struct mailbox
*mp
);
61 static const char infofmt
[] = "%c %lu %d %lu %ld";
64 ((f) & (MSAVED|MDELETED|MREAD|MBOXED|MNEW|MFLAGGED|MANSWERED|MDRAFTED))
66 static const char README1
[] = "\
67 This is a cache directory maintained by " UAGENT
"(1). You should not change any\n\
68 files within. Nevertheless, the structure is as follows: Each subdirectory\n\
69 of the current directory represents an IMAP account, and each subdirectory\n\
70 below that represents a mailbox. Each mailbox directory contains a file\n\
71 named UIDVALIDITY which describes the validity in relation to the version\n\
72 on the server. Other files have names corresponding to their IMAP UID.\n";
73 static const char README2
[] = "\n\
74 The first 128 bytes of these files are used to store message attributes; the\n\
75 following data is equivalent to compress(1) output. So if you have to save a\n\
76 message by hand because of an emergency, throw away the first 128 bytes and\n\
77 decompress the rest, as e.g. 'dd if=MESSAGEFILE skip=1 bs=128 | zcat' does.\n";
78 static const char README3
[] = "\n\
79 Files named QUEUE contain data that will be sent do the IMAP server next\n\
80 time a connection is made in online mode.\n";
81 static const char README4
[] = "\n\
82 You can safely delete any file or directory here, unless it contains a QUEUE\n\
83 file that is not empty; mailx(1) will download the data again and will also\n\
84 write new cache entries if configured in this way. If you do not wish to use\n\
85 the cache anymore, delete the entire directory and unset the 'imap-cache'\n\
86 variable in mailx(1).\n";
87 static const char README5
[] = "\n\
88 For more information about " UAGENT
"(1), visit\n\
89 <http://sdaoden.users.sourceforge.net/code.html>.\n"; /* TODO MAGIC CONSTANT */
92 encname(struct mailbox
*mp
, const char *name
, int same
, const char *box
)
94 char *cachedir
, *eaccount
, *ename
, *res
;
98 ename
= urlxenc(name
);
99 if (mp
->mb_cache_directory
&& same
&& box
== NULL
) {
100 res
= salloc(resz
= strlen(mp
->mb_cache_directory
) +
102 snprintf(res
, resz
, "%s%s%s", mp
->mb_cache_directory
,
103 *ename
? "/" : "", ename
);
105 if ((cachedir
= ok_vlook(imap_cache
)) == NULL
||
106 (cachedir
= file_expand(cachedir
)) == NULL
)
108 eaccount
= urlxenc(mp
->mb_imap_account
);
110 emailbox
= urlxenc(box
);
111 else if (asccasecmp(mp
->mb_imap_mailbox
, "INBOX"))
112 emailbox
= urlxenc(mp
->mb_imap_mailbox
);
115 res
= salloc(resz
= strlen(cachedir
) + strlen(eaccount
) +
116 strlen(emailbox
) + strlen(ename
) + 4);
117 snprintf(res
, resz
, "%s/%s/%s%s%s",
118 cachedir
, eaccount
, emailbox
,
119 *ename
? "/" : "", ename
);
125 encuid(struct mailbox
*mp
, unsigned long uid
)
129 snprintf(buf
, sizeof buf
, "%lu", uid
);
130 return encname(mp
, buf
, 1, NULL
);
134 getcache1(struct mailbox
*mp
, struct message
*m
, enum needspec need
,
138 long n
= 0, size
= 0, xsize
, xtime
, xlines
= -1, lines
= 0;
139 int lastc
= EOF
, i
, xflag
, inheader
= 1;
144 if (setflags
== 0 && ((mp
->mb_type
!= MB_IMAP
&&
145 mp
->mb_type
!= MB_CACHE
) ||
148 if ((fp
= Fopen(encuid(mp
, m
->m_uid
), "r")) == NULL
)
150 (void)fcntl_lock(fileno(fp
), F_RDLCK
);
151 if (fscanf(fp
, infofmt
, &b
, (unsigned long*)&xsize
, &xflag
,
152 (unsigned long*)&xtime
, &xlines
) < 4)
154 if (need
!= NEED_UNSPEC
) {
157 if (need
== NEED_HEADER
)
161 if (need
== NEED_HEADER
|| need
== NEED_BODY
)
171 if (fseek(fp
, INITSKIP
, SEEK_SET
) < 0)
174 if (fseek(mp
->mb_otf
, 0L, SEEK_END
) < 0) {
178 offset
= ftell(mp
->mb_otf
);
179 while (inheader
&& (n
= zread(zp
, iob
, sizeof iob
)) > 0) {
181 for (i
= 0; i
< n
; i
++) {
182 if (iob
[i
] == '\n') {
189 fwrite(iob
, 1, n
, mp
->mb_otf
);
191 if (n
> 0 && need
== NEED_BODY
) {
192 while ((n
= zread(zp
, iob
, sizeof iob
)) > 0) {
194 for (i
= 0; i
< n
; i
++)
197 fwrite(iob
, 1, n
, mp
->mb_otf
);
201 if (zfree(zp
) < 0 || n
< 0 || ferror(fp
) || ferror(mp
->mb_otf
))
205 m
->m_block
= mailx_blockof(offset
);
206 m
->m_offset
= mailx_offsetof(offset
);
207 flags
: if (setflags
) {
211 m
->m_flag
= xflag
| MNOFROM
;
213 m
->m_flag
|= MHIDDEN
;
216 if (xlines
> 0 && m
->m_xlines
<= 0)
217 m
->m_xlines
= xlines
;
221 if (xflag
== MREAD
&& xlines
> 0)
222 m
->m_flag
|= MFULLYCACHED
;
223 if (need
== NEED_BODY
) {
224 m
->m_have
|= HAVE_HEADER
|HAVE_BODY
;
226 m
->m_xlines
= m
->m_lines
;
231 m
->m_have
|= HAVE_HEADER
;
244 getcache(struct mailbox
*mp
, struct message
*m
, enum needspec need
)
246 return getcache1(mp
, m
, need
, 0);
250 putcache(struct mailbox
*mp
, struct message
*m
)
255 long n
, cnt
, oldoffset
, osize
, otime
, olines
= -1;
259 if ((mp
->mb_type
!= MB_IMAP
&& mp
->mb_type
!= MB_CACHE
) ||
260 m
->m_uid
== 0 || m
->m_time
== 0 ||
261 (m
->m_flag
& (MTOUCH
|MFULLYCACHED
)) == MFULLYCACHED
)
263 if (m
->m_have
& HAVE_BODY
)
265 else if (m
->m_have
& HAVE_HEADER
)
267 else if (m
->m_have
== HAVE_NOTHING
)
271 if ((oldoffset
= ftell(mp
->mb_itf
)) < 0) /* XXX weird err hdling */
273 if ((obuf
= Fopen(name
= encuid(mp
, m
->m_uid
), "r+")) == NULL
) {
274 if ((obuf
= Fopen(name
, "w")) == NULL
)
276 (void)fcntl_lock(fileno(obuf
), F_WRLCK
); /* XXX err hdl */
278 (void)fcntl_lock(fileno(obuf
), F_WRLCK
); /* XXX err hdl */
279 if (fscanf(obuf
, infofmt
, &ob
, (unsigned long*)&osize
, &oflag
,
280 (unsigned long*)&otime
, &olines
) >= 4 &&
281 ob
!= '\0' && (ob
== 'B' ||
282 (ob
== 'H' && c
!= 'B'))) {
283 if (m
->m_xlines
<= 0 && olines
> 0)
284 m
->m_xlines
= olines
;
285 if ((c
!= 'N' && (size_t)osize
!= m
->m_xsize
) ||
286 oflag
!= (int)USEBITS(m
->m_flag
) ||
287 otime
!= m
->m_time
||
289 olines
!= m
->m_xlines
)) {
292 fprintf(obuf
, infofmt
, ob
,
293 (unsigned long)m
->m_xsize
,
295 (unsigned long)m
->m_time
,
304 ftruncate(fileno(obuf
), 0);
306 if ((ibuf
= setinput(mp
, m
, NEED_UNSPEC
)) == NULL
) {
312 fseek(obuf
, INITSKIP
, SEEK_SET
);
316 n
= cnt
> (long)sizeof iob
? (long)sizeof iob
: cnt
;
318 if ((size_t)n
!= fread(iob
, 1, n
, ibuf
) ||
319 n
!= (long)zwrite(zp
, iob
, n
)) {
330 fprintf(obuf
, infofmt
, c
, (unsigned long)m
->m_xsize
,
332 (unsigned long)m
->m_time
,
339 if (c
== 'B' && USEBITS(m
->m_flag
) == MREAD
)
340 m
->m_flag
|= MFULLYCACHED
;
341 out
: if (Fclose(obuf
) != 0) {
342 m
->m_flag
&= ~MFULLYCACHED
;
345 (void)fseek(mp
->mb_itf
, oldoffset
, SEEK_SET
);
349 initcache(struct mailbox
*mp
)
356 if (mp
->mb_cache_directory
!= NULL
)
357 free(mp
->mb_cache_directory
);
358 mp
->mb_cache_directory
= NULL
;
359 if ((name
= encname(mp
, "", 1, NULL
)) == NULL
)
361 mp
->mb_cache_directory
= sstrdup(name
);
362 if ((uvname
= encname(mp
, "UIDVALIDITY", 1, NULL
)) == NULL
)
364 if (cwget(&cw
) == STOP
)
366 if ((uvfp
= Fopen(uvname
, "r+")) == NULL
||
367 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
368 fscanf(uvfp
, "%lu", &uv
) != 1 ||
369 uv
!= mp
->mb_uidvalidity
) {
370 if ((uvfp
= clean(mp
, &cw
)) == NULL
)
376 fcntl_lock(fileno(uvfp
), F_WRLCK
);
377 fprintf(uvfp
, "%lu\n", mp
->mb_uidvalidity
);
378 if (ferror(uvfp
) || Fclose(uvfp
) != 0) {
380 mp
->mb_uidvalidity
= 0;
386 purgecache(struct mailbox
*mp
, struct message
*m
, long mc
)
391 if ((name
= encname(mp
, "", 1, NULL
)) == NULL
)
393 if (cwget(&cw
) == STOP
)
395 purge(mp
, m
, mc
, &cw
, name
);
400 clean(struct mailbox
*mp
, struct cw
*cw
)
402 char *cachedir
, *eaccount
, *buf
;
403 char const *emailbox
;
409 if ((cachedir
= ok_vlook(imap_cache
)) == NULL
||
410 (cachedir
= file_expand(cachedir
)) == NULL
)
412 eaccount
= urlxenc(mp
->mb_imap_account
);
413 if (asccasecmp(mp
->mb_imap_mailbox
, "INBOX"))
414 emailbox
= urlxenc(mp
->mb_imap_mailbox
);
417 buf
= salloc(bufsz
= strlen(cachedir
) + strlen(eaccount
) +
418 strlen(emailbox
) + 40);
419 if (makedir(cachedir
) != OKAY
)
421 snprintf(buf
, bufsz
, "%s/README", cachedir
);
422 if ((fp
= Fopen(buf
, "wx")) != NULL
) {
430 snprintf(buf
, bufsz
, "%s/%s", cachedir
, eaccount
);
431 if (makedir(buf
) != OKAY
)
433 snprintf(buf
, bufsz
, "%s/%s/%s", cachedir
, eaccount
, emailbox
);
434 if (makedir(buf
) != OKAY
)
438 if ((dirp
= opendir(".")) == NULL
)
440 while ((dp
= readdir(dirp
)) != NULL
) {
441 if (dp
->d_name
[0] == '.' &&
442 (dp
->d_name
[1] == '\0' ||
443 (dp
->d_name
[1] == '.' &&
444 dp
->d_name
[2] == '\0')))
449 fp
= Fopen("UIDVALIDITY", "w");
450 out
: if (cwret(cw
) == STOP
) {
451 fputs("Fatal: Cannot change back to current directory.\n",
458 static unsigned long *
459 builds(long *contentelem
)
461 unsigned long n
, *contents
= NULL
;
462 long contentalloc
= 0;
468 if ((dirp
= opendir(".")) == NULL
)
470 while ((dp
= readdir(dirp
)) != NULL
) {
471 if (dp
->d_name
[0] == '.' &&
472 (dp
->d_name
[1] == '\0' ||
473 (dp
->d_name
[1] == '.' &&
474 dp
->d_name
[2] == '\0')))
476 n
= strtoul(dp
->d_name
, &x
, 10);
479 if (*contentelem
>= contentalloc
- 1)
480 contents
= srealloc(contents
,
481 (contentalloc
+= 200) * sizeof *contents
);
482 contents
[(*contentelem
)++] = n
;
485 if (*contentelem
> 0) {
486 contents
[*contentelem
] = 0;
487 qsort(contents
, *contentelem
, sizeof *contents
, longlt
);
493 purge(struct mailbox
*mp
, struct message
*m
, long mc
, struct cw
*cw
,
496 unsigned long *contents
;
497 long i
, j
, contentelem
;
502 contents
= builds(&contentelem
);
505 while (j
< contentelem
) {
506 if (i
< mc
&& m
[i
].m_uid
== contents
[j
]) {
509 } else if (i
< mc
&& m
[i
].m_uid
< contents
[j
])
512 remve(contents
[j
++]);
515 if (cwret(cw
) == STOP
) {
516 fputs("Fatal: Cannot change back to current directory.\n",
524 longlt(const void *a
, const void *b
)
526 return *(const long*)a
- *(const long*)b
;
530 remve(unsigned long n
)
534 snprintf(buf
, sizeof buf
, "%lu", n
);
539 delcache(struct mailbox
*mp
, struct message
*m
)
543 fn
= encuid(mp
, m
->m_uid
);
544 if (fn
&& unlink(fn
) == 0)
545 m
->m_flag
|= MUNLINKED
;
549 cache_setptr(int transparent
)
554 unsigned long *contents
;
557 struct message
*omessage
= NULL
;
562 omsgCount
= msgCount
;
564 free(mb
.mb_cache_directory
);
565 mb
.mb_cache_directory
= NULL
;
566 if ((name
= encname(&mb
, "", 1, NULL
)) == NULL
)
568 mb
.mb_cache_directory
= sstrdup(name
);
569 if (cwget(&cw
) == STOP
)
573 contents
= builds(&contentelem
);
574 msgCount
= contentelem
;
575 message
= scalloc(msgCount
+ 1, sizeof *message
);
576 if (cwret(&cw
) == STOP
) {
577 fputs("Fatal: Cannot change back to current directory.\n",
582 for (i
= 0; i
< msgCount
; i
++) {
583 message
[i
].m_uid
= contents
[i
];
584 getcache1(&mb
, &message
[i
], NEED_UNSPEC
, 3);
588 mb
.mb_type
= MB_CACHE
;
589 mb
.mb_perm
= (options
& OPT_R_FLAG
) ? 0 : MB_DELE
;
591 transflags(omessage
, omsgCount
, 1);
599 cache_list(struct mailbox
*mp
, const char *base
, int strip
, FILE *fp
)
601 char *name
, *cachedir
, *eaccount
;
604 const char *cp
, *bp
, *sp
;
607 if ((cachedir
= ok_vlook(imap_cache
)) == NULL
||
608 (cachedir
= file_expand(cachedir
)) == NULL
)
610 eaccount
= urlxenc(mp
->mb_imap_account
);
611 name
= salloc(namesz
= strlen(cachedir
) + strlen(eaccount
) + 2);
612 snprintf(name
, namesz
, "%s/%s", cachedir
, eaccount
);
613 if ((dirp
= opendir(name
)) == NULL
)
615 while ((dp
= readdir(dirp
)) != NULL
) {
616 if (dp
->d_name
[0] == '.')
618 cp
= sp
= urlxdec(dp
->d_name
);
619 for (bp
= base
; *bp
&& *bp
== *sp
; bp
++)
623 cp
= strip
? sp
: cp
;
624 fprintf(fp
, "%s\n", *cp
? cp
: "INBOX");
631 cache_remove(const char *name
)
637 int pathsize
, pathend
, n
;
640 if ((dir
= encname(&mb
, "", 0, imap_fileof(name
))) == NULL
)
642 pathend
= strlen(dir
);
643 path
= smalloc(pathsize
= pathend
+ 30);
644 memcpy(path
, dir
, pathend
);
645 path
[pathend
++] = '/';
646 path
[pathend
] = '\0';
647 if ((dirp
= opendir(path
)) == NULL
) {
651 while ((dp
= readdir(dirp
)) != NULL
) {
652 if (dp
->d_name
[0] == '.' &&
653 (dp
->d_name
[1] == '\0' ||
654 (dp
->d_name
[1] == '.' &&
655 dp
->d_name
[2] == '\0')))
657 n
= strlen(dp
->d_name
) + 1;
658 if (pathend
+ n
> pathsize
)
659 path
= srealloc(path
, pathsize
= pathend
+ n
+ 30);
660 memcpy(path
+ pathend
, dp
->d_name
, n
);
661 if (stat(path
, &st
) < 0 || (st
.st_mode
&S_IFMT
) != S_IFREG
)
663 if (unlink(path
) < 0) {
671 path
[pathend
] = '\0';
672 rmdir(path
); /* no error on failure, might contain submailboxes */
678 cache_rename(const char *old
, const char *new)
680 char *olddir
, *newdir
;
682 if ((olddir
= encname(&mb
, "", 0, imap_fileof(old
))) == NULL
||
683 (newdir
= encname(&mb
, "",0, imap_fileof(new))) == NULL
)
685 if (rename(olddir
, newdir
) < 0) {
693 cached_uidvalidity(struct mailbox
*mp
)
699 if ((uvname
= encname(mp
, "UIDVALIDITY", 1, NULL
)) == NULL
)
701 if ((uvfp
= Fopen(uvname
, "r")) == NULL
||
702 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
703 fscanf(uvfp
, "%lu", &uv
) != 1)
711 cache_queue1(struct mailbox
*mp
, char const *mode
, char **xname
)
716 if ((name
= encname(mp
, "QUEUE", 0, NULL
)) == NULL
)
718 if ((fp
= Fopen(name
, mode
)) != NULL
)
719 fcntl_lock(fileno(fp
), F_WRLCK
);
726 cache_queue(struct mailbox
*mp
)
730 fp
= cache_queue1(mp
, "a", NULL
);
732 fputs("Cannot queue IMAP command. Retry when online.\n",
738 cache_dequeue(struct mailbox
*mp
)
741 char *cachedir
, *eaccount
, *buf
, *oldbox
;
745 if ((cachedir
= ok_vlook(imap_cache
)) == NULL
||
746 (cachedir
= file_expand(cachedir
)) == NULL
)
748 eaccount
= urlxenc(mp
->mb_imap_account
);
749 buf
= salloc(bufsz
= strlen(cachedir
) + strlen(eaccount
) + 2);
750 snprintf(buf
, bufsz
, "%s/%s", cachedir
, eaccount
);
751 if ((dirp
= opendir(buf
)) == NULL
)
753 oldbox
= mp
->mb_imap_mailbox
;
754 while ((dp
= readdir(dirp
)) != NULL
) {
755 if (dp
->d_name
[0] == '.')
757 mp
->mb_imap_mailbox
= urlxdec(dp
->d_name
);
761 mp
->mb_imap_mailbox
= oldbox
;
766 dequeue1(struct mailbox
*mp
)
768 FILE *fp
= NULL
, *uvfp
= NULL
;
769 char *qname
, *uvname
;
774 fp
= cache_queue1(mp
, "r+", &qname
);
775 if (fp
!= NULL
&& fsize(fp
) > 0) {
776 if (imap_select(mp
, &is_size
, &is_count
,
777 mp
->mb_imap_mailbox
) != OKAY
) {
778 fprintf(stderr
, "Cannot select \"%s\" for dequeuing.\n",
779 mp
->mb_imap_mailbox
);
782 if ((uvname
= encname(mp
, "UIDVALIDITY", 0, NULL
)) == NULL
||
783 (uvfp
= Fopen(uvname
, "r")) == NULL
||
784 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
785 fscanf(uvfp
, "%lu", &uv
) != 1 ||
786 uv
!= mp
->mb_uidvalidity
) {
788 "Unique identifiers for \"%s\" are out of date. "
789 "Cannot commit IMAP commands.\n",
790 mp
->mb_imap_mailbox
);
791 save
: fputs("Saving IMAP commands to dead.letter\n", stderr
);
792 savedeadletter(fp
, 0);
793 ftruncate(fileno(fp
), 0);
800 printf("Committing IMAP commands for \"%s\"\n",
801 mp
->mb_imap_mailbox
);
802 imap_dequeue(mp
, fp
);
810 #endif /* HAVE_IMAP */