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
)
48 static char * encname(struct mailbox
*mp
, const char *name
, int same
,
50 static char * encuid(struct mailbox
*mp
, unsigned long uid
);
51 static FILE * clean(struct mailbox
*mp
, struct cw
*cw
);
52 static unsigned long * builds(long *contentelem
);
53 static void purge(struct mailbox
*mp
, struct message
*m
, long mc
,
54 struct cw
*cw
, const char *name
);
55 static int longlt(const void *a
, const void *b
);
56 static void remve(unsigned long n
);
57 static FILE * cache_queue1(struct mailbox
*mp
, char const *mode
,
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).\n\
68 You should not change any files within.\n\
69 Nevertheless, the structure is as follows: Each subdirectory of the\n\
70 current directory represents an IMAP account, and each subdirectory\n\
71 below that represents a mailbox. Each mailbox directory contains a file\n\
72 named UIDVALIDITY which describes the validity in relation to the version\n\
73 on the server. Other files have names corresponding to their IMAP UID.\n";
74 static const char README2
[] = "\n\
75 The first 128 bytes of these files are used to store message attributes; the\n\
76 following data is equivalent to compress(1) output. So if you have to save a\n\
77 message by hand because of an emergency, throw away the first 128 bytes and\n\
78 decompress the rest, as e.g. 'dd if=MESSAGEFILE skip=1 bs=128 | zcat' does.\n";
79 static const char README3
[] = "\n\
80 Files named QUEUE contain data that will be sent do the IMAP server next\n\
81 time a connection is made in online mode.\n";
82 static const char README4
[] = "\n\
83 You can safely delete any file or directory here, unless it contains a QUEUE\n\
84 file that is not empty; " UAGENT
85 "mailx(1) will download the data again and will also\n\
86 write new cache entries if configured in this way. If you do not wish to use\n\
87 the cache anymore, delete the entire directory and unset the 'imap-cache'\n\
88 variable in " UAGENT
"(1).\n";
89 static const char README5
[] = "\n\
90 For more information about " UAGENT
"(1), visit\n\
91 <http://sdaoden.users.sourceforge.net/code.html>.\n"; /* TODO MAGIC CONSTANT */
94 encname(struct mailbox
*mp
, const char *name
, int same
, const char *box
)
96 char *cachedir
, *eaccount
, *ename
, *res
;
101 ename
= urlxenc(name
);
102 if (mp
->mb_cache_directory
&& same
&& box
== NULL
) {
103 res
= salloc(resz
= strlen(mp
->mb_cache_directory
) + strlen(ename
) + 2);
104 snprintf(res
, resz
, "%s%s%s", mp
->mb_cache_directory
,
105 (*ename
? "/" : ""), ename
);
107 if ((cachedir
= ok_vlook(imap_cache
)) == NULL
||
108 (cachedir
= file_expand(cachedir
)) == NULL
) {
112 eaccount
= urlxenc(mp
->mb_imap_account
);
114 emailbox
= urlxenc(box
);
115 else if (asccasecmp(mp
->mb_imap_mailbox
, "INBOX"))
116 emailbox
= urlxenc(mp
->mb_imap_mailbox
);
119 res
= salloc(resz
= strlen(cachedir
) + strlen(eaccount
) +
120 strlen(emailbox
) + strlen(ename
) + 4);
121 snprintf(res
, resz
, "%s/%s/%s%s%s", cachedir
, eaccount
, emailbox
,
122 (*ename
? "/" : ""), ename
);
130 encuid(struct mailbox
*mp
, unsigned long uid
)
135 snprintf(buf
, sizeof buf
, "%lu", uid
);
136 cp
= encname(mp
, buf
, 1, NULL
);
142 getcache1(struct mailbox
*mp
, struct message
*m
, enum needspec need
,
146 long n
= 0, size
= 0, xsize
, xtime
, xlines
= -1, lines
= 0;
147 int lastc
= EOF
, i
, xflag
, inheader
= 1;
154 if (setflags
== 0 && ((mp
->mb_type
!= MB_IMAP
&& mp
->mb_type
!= MB_CACHE
) ||
157 if ((fp
= Fopen(encuid(mp
, m
->m_uid
), "r")) == NULL
)
160 fcntl_lock(fileno(fp
), FLOCK_READ
);
161 if (fscanf(fp
, infofmt
, &b
, (unsigned long*)&xsize
, &xflag
,
162 (unsigned long*)&xtime
, &xlines
) < 4)
164 if (need
!= NEED_UNSPEC
) {
167 if (need
== NEED_HEADER
)
171 if (need
== NEED_HEADER
|| need
== NEED_BODY
)
181 if (fseek(fp
, INITSKIP
, SEEK_SET
) < 0)
184 if (fseek(mp
->mb_otf
, 0L, SEEK_END
) < 0) {
188 offset
= ftell(mp
->mb_otf
);
189 while (inheader
&& (n
= zread(zp
, iob
, sizeof iob
)) > 0) {
191 for (i
= 0; i
< n
; i
++) {
192 if (iob
[i
] == '\n') {
199 fwrite(iob
, 1, n
, mp
->mb_otf
);
201 if (n
> 0 && need
== NEED_BODY
) {
202 while ((n
= zread(zp
, iob
, sizeof iob
)) > 0) {
204 for (i
= 0; i
< n
; i
++)
207 fwrite(iob
, 1, n
, mp
->mb_otf
);
211 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
);
223 m
->m_flag
= xflag
| MNOFROM
;
225 m
->m_flag
|= MHIDDEN
;
228 if (xlines
> 0 && m
->m_xlines
<= 0)
229 m
->m_xlines
= xlines
;
233 if (xflag
== MREAD
&& xlines
> 0)
234 m
->m_flag
|= MFULLYCACHED
;
235 if (need
== NEED_BODY
) {
236 m
->m_have
|= HAVE_HEADER
| HAVE_BODY
;
238 m
->m_xlines
= m
->m_lines
;
243 m
->m_have
|= HAVE_HEADER
;
257 getcache(struct mailbox
*mp
, struct message
*m
, enum needspec need
)
262 rv
= getcache1(mp
, m
, need
, 0);
268 putcache(struct mailbox
*mp
, struct message
*m
)
270 char iob
[32768], *name
, ob
;
273 long n
, cnt
, oldoffset
, osize
, otime
, olines
= -1;
277 if ((mp
->mb_type
!= MB_IMAP
&& mp
->mb_type
!= MB_CACHE
) || m
->m_uid
== 0 ||
278 m
->m_time
== 0 || (m
->m_flag
& (MTOUCH
|MFULLYCACHED
)) == MFULLYCACHED
)
280 if (m
->m_have
& HAVE_BODY
)
282 else if (m
->m_have
& HAVE_HEADER
)
284 else if (m
->m_have
== HAVE_NOTHING
)
288 if ((oldoffset
= ftell(mp
->mb_itf
)) < 0) /* XXX weird err hdling */
290 if ((obuf
= Fopen(name
= encuid(mp
, m
->m_uid
), "r+")) == NULL
) {
291 if ((obuf
= Fopen(name
, "w")) == NULL
)
293 fcntl_lock(fileno(obuf
), FLOCK_WRITE
); /* XXX err hdl */
295 fcntl_lock(fileno(obuf
), FLOCK_READ
); /* XXX err hdl */
296 if (fscanf(obuf
, infofmt
, &ob
, (unsigned long*)&osize
, &oflag
,
297 (unsigned long*)&otime
, &olines
) >= 4 && ob
!= '\0' &&
298 (ob
== 'B' || (ob
== 'H' && c
!= 'B'))) {
299 if (m
->m_xlines
<= 0 && olines
> 0)
300 m
->m_xlines
= olines
;
301 if ((c
!= 'N' && (size_t)osize
!= m
->m_xsize
) ||
302 oflag
!= (int)USEBITS(m
->m_flag
) || otime
!= m
->m_time
||
303 (m
->m_xlines
> 0 && olines
!= m
->m_xlines
)) {
306 fprintf(obuf
, infofmt
, ob
, (unsigned long)m
->m_xsize
,
307 USEBITS(m
->m_flag
), (unsigned long)m
->m_time
, m
->m_xlines
);
315 ftruncate(fileno(obuf
), 0);
317 if ((ibuf
= setinput(mp
, m
, NEED_UNSPEC
)) == NULL
) {
323 fseek(obuf
, INITSKIP
, SEEK_SET
);
327 n
= (cnt
> (long)sizeof iob
) ? (long)sizeof iob
: cnt
;
329 if ((size_t)n
!= fread(iob
, 1, n
, ibuf
) ||
330 n
!= (long)zwrite(zp
, iob
, n
)) {
342 fprintf(obuf
, infofmt
, c
, (unsigned long)m
->m_xsize
, USEBITS(m
->m_flag
),
343 (unsigned long)m
->m_time
, m
->m_xlines
);
349 if (c
== 'B' && USEBITS(m
->m_flag
) == MREAD
)
350 m
->m_flag
|= MFULLYCACHED
;
352 if (Fclose(obuf
) != 0) {
353 m
->m_flag
&= ~MFULLYCACHED
;
356 (void)fseek(mp
->mb_itf
, oldoffset
, SEEK_SET
);
362 initcache(struct mailbox
*mp
)
370 if (mp
->mb_cache_directory
!= NULL
)
371 free(mp
->mb_cache_directory
);
372 mp
->mb_cache_directory
= NULL
;
373 if ((name
= encname(mp
, "", 1, NULL
)) == NULL
)
375 mp
->mb_cache_directory
= sstrdup(name
);
376 if ((uvname
= encname(mp
, "UIDVALIDITY", 1, NULL
)) == NULL
)
378 if (cwget(&cw
) == STOP
)
380 if ((uvfp
= Fopen(uvname
, "r+")) == NULL
||
381 (fcntl_lock(fileno(uvfp
), FLOCK_READ
), 0) ||
382 fscanf(uvfp
, "%lu", &uv
) != 1 || uv
!= mp
->mb_uidvalidity
) {
383 if ((uvfp
= clean(mp
, &cw
)) == NULL
)
389 fcntl_lock(fileno(uvfp
), FLOCK_WRITE
);
390 fprintf(uvfp
, "%lu\n", mp
->mb_uidvalidity
);
391 if (ferror(uvfp
) || Fclose(uvfp
) != 0) {
393 mp
->mb_uidvalidity
= 0;
402 purgecache(struct mailbox
*mp
, struct message
*m
, long mc
)
408 if ((name
= encname(mp
, "", 1, NULL
)) == NULL
)
410 if (cwget(&cw
) == STOP
)
412 purge(mp
, m
, mc
, &cw
, name
);
419 clean(struct mailbox
*mp
, struct cw
*cw
)
421 char *cachedir
, *eaccount
, *buf
;
422 char const *emailbox
;
429 if ((cachedir
= ok_vlook(imap_cache
)) == NULL
||
430 (cachedir
= file_expand(cachedir
)) == NULL
)
432 eaccount
= urlxenc(mp
->mb_imap_account
);
433 if (asccasecmp(mp
->mb_imap_mailbox
, "INBOX"))
434 emailbox
= urlxenc(mp
->mb_imap_mailbox
);
437 buf
= salloc(bufsz
= strlen(cachedir
) + strlen(eaccount
) +
438 strlen(emailbox
) + 40);
439 if (makedir(cachedir
) != OKAY
)
441 snprintf(buf
, bufsz
, "%s/README", cachedir
);
442 if ((fp
= Fopen(buf
, "wx")) != NULL
) {
451 snprintf(buf
, bufsz
, "%s/%s", cachedir
, eaccount
);
452 if (makedir(buf
) != OKAY
)
454 snprintf(buf
, bufsz
, "%s/%s/%s", cachedir
, eaccount
, emailbox
);
455 if (makedir(buf
) != OKAY
)
459 if ((dirp
= opendir(".")) == NULL
)
461 while ((dp
= readdir(dirp
)) != NULL
) {
462 if (dp
->d_name
[0] == '.' && (dp
->d_name
[1] == '\0' ||
463 (dp
->d_name
[1] == '.' && dp
->d_name
[2] == '\0')))
468 fp
= Fopen("UIDVALIDITY", "w");
470 if (cwret(cw
) == STOP
) {
471 fputs("Fatal: Cannot change back to current directory.\n", stderr
);
479 static unsigned long *
480 builds(long *contentelem
)
482 unsigned long n
, *contents
= NULL
;
483 long contentalloc
= 0;
490 if ((dirp
= opendir(".")) == NULL
)
492 while ((dp
= readdir(dirp
)) != NULL
) {
493 if (dp
->d_name
[0] == '.' && (dp
->d_name
[1] == '\0' ||
494 (dp
->d_name
[1] == '.' && dp
->d_name
[2] == '\0')))
496 n
= strtoul(dp
->d_name
, &x
, 10);
499 if (*contentelem
>= contentalloc
- 1)
500 contents
= srealloc(contents
,
501 (contentalloc
+= 200) * sizeof *contents
);
502 contents
[(*contentelem
)++] = n
;
505 if (*contentelem
> 0) {
506 contents
[*contentelem
] = 0;
507 qsort(contents
, *contentelem
, sizeof *contents
, longlt
);
515 purge(struct mailbox
*mp
, struct message
*m
, long mc
, struct cw
*cw
,
518 unsigned long *contents
;
519 long i
, j
, contentelem
;
525 contents
= builds(&contentelem
);
528 while (j
< contentelem
) {
529 if (i
< mc
&& m
[i
].m_uid
== contents
[j
]) {
532 } else if (i
< mc
&& m
[i
].m_uid
< contents
[j
])
535 remve(contents
[j
++]);
538 if (cwret(cw
) == STOP
) {
539 fputs("Fatal: Cannot change back to current directory.\n", stderr
);
548 longlt(const void *a
, const void *b
)
550 union {long l
; int i
;} u
;
553 u
.l
= *(long const*)a
- *(long const*)b
;
554 u
.i
= (u
.l
< 0) ? -1 : ((u
.l
> 0) ? 1 : 0);
560 remve(unsigned long n
)
565 snprintf(buf
, sizeof buf
, "%lu", n
);
571 delcache(struct mailbox
*mp
, struct message
*m
)
576 fn
= encuid(mp
, m
->m_uid
);
577 if (fn
&& unlink(fn
) == 0)
578 m
->m_flag
|= MUNLINKED
;
583 cache_setptr(int transparent
)
586 int i
, omsgCount
= 0;
588 unsigned long *contents
;
590 struct message
*omessage
= NULL
;
596 omsgCount
= msgCount
;
598 free(mb
.mb_cache_directory
);
599 mb
.mb_cache_directory
= NULL
;
600 if ((name
= encname(&mb
, "", 1, NULL
)) == NULL
)
602 mb
.mb_cache_directory
= sstrdup(name
);
603 if (cwget(&cw
) == STOP
)
607 contents
= builds(&contentelem
);
608 msgCount
= contentelem
;
609 message
= scalloc(msgCount
+ 1, sizeof *message
);
610 if (cwret(&cw
) == STOP
) {
611 fputs("Fatal: Cannot change back to current directory.\n", stderr
);
615 for (i
= 0; i
< msgCount
; i
++) {
616 message
[i
].m_uid
= contents
[i
];
617 getcache1(&mb
, &message
[i
], NEED_UNSPEC
, 3);
619 mb
.mb_type
= MB_CACHE
;
620 mb
.mb_perm
= (options
& OPT_R_FLAG
) ? 0 : MB_DELE
;
622 transflags(omessage
, omsgCount
, 1);
632 cache_list(struct mailbox
*mp
, const char *base
, int strip
, FILE *fp
)
634 char *name
, *cachedir
, *eaccount
;
637 const char *cp
, *bp
, *sp
;
642 if ((cachedir
= ok_vlook(imap_cache
)) == NULL
||
643 (cachedir
= file_expand(cachedir
)) == NULL
)
645 eaccount
= urlxenc(mp
->mb_imap_account
);
646 name
= salloc(namesz
= strlen(cachedir
) + strlen(eaccount
) + 2);
647 snprintf(name
, namesz
, "%s/%s", cachedir
, eaccount
);
648 if ((dirp
= opendir(name
)) == NULL
)
650 while ((dp
= readdir(dirp
)) != NULL
) {
651 if (dp
->d_name
[0] == '.')
653 cp
= sp
= urlxdec(dp
->d_name
);
654 for (bp
= base
; *bp
&& *bp
== *sp
; bp
++)
658 cp
= strip
? sp
: cp
;
659 fprintf(fp
, "%s\n", *cp
? cp
: "INBOX");
669 cache_remove(const char *name
)
675 int pathsize
, pathend
, n
;
679 if ((dir
= encname(&mb
, "", 0, imap_fileof(name
))) == NULL
)
681 pathend
= strlen(dir
);
682 path
= smalloc(pathsize
= pathend
+ 30);
683 memcpy(path
, dir
, pathend
);
684 path
[pathend
++] = '/';
685 path
[pathend
] = '\0';
686 if ((dirp
= opendir(path
)) == NULL
) {
690 while ((dp
= readdir(dirp
)) != NULL
) {
691 if (dp
->d_name
[0] == '.' && (dp
->d_name
[1] == '\0' ||
692 (dp
->d_name
[1] == '.' && dp
->d_name
[2] == '\0')))
694 n
= strlen(dp
->d_name
) + 1;
695 if (pathend
+ n
> pathsize
)
696 path
= srealloc(path
, pathsize
= pathend
+ n
+ 30);
697 memcpy(path
+ pathend
, dp
->d_name
, n
);
698 if (stat(path
, &st
) < 0 || (st
.st_mode
& S_IFMT
) != S_IFREG
)
700 if (unlink(path
) < 0) {
709 path
[pathend
] = '\0';
710 rmdir(path
); /* no error on failure, might contain submailboxes */
718 cache_rename(const char *old
, const char *new)
720 char *olddir
, *newdir
;
724 if ((olddir
= encname(&mb
, "", 0, imap_fileof(old
))) == NULL
||
725 (newdir
= encname(&mb
, "",0, imap_fileof(new))) == NULL
)
727 if (rename(olddir
, newdir
) < 0) {
737 cached_uidvalidity(struct mailbox
*mp
)
744 if ((uvname
= encname(mp
, "UIDVALIDITY", 1, NULL
)) == NULL
) {
748 if ((uvfp
= Fopen(uvname
, "r")) == NULL
||
749 (fcntl_lock(fileno(uvfp
), FLOCK_READ
), 0) ||
750 fscanf(uvfp
, "%lu", &uv
) != 1)
760 cache_queue1(struct mailbox
*mp
, char const *mode
, char **xname
)
766 if ((name
= encname(mp
, "QUEUE", 0, NULL
)) == NULL
)
768 if ((fp
= Fopen(name
, mode
)) != NULL
)
769 fcntl_lock(fileno(fp
), FLOCK_WRITE
);
778 cache_queue(struct mailbox
*mp
)
783 fp
= cache_queue1(mp
, "a", NULL
);
785 fputs("Cannot queue IMAP command. Retry when online.\n", stderr
);
791 cache_dequeue(struct mailbox
*mp
)
794 char *cachedir
, *eaccount
, *buf
, *oldbox
;
800 if ((cachedir
= ok_vlook(imap_cache
)) == NULL
||
801 (cachedir
= file_expand(cachedir
)) == NULL
)
803 eaccount
= urlxenc(mp
->mb_imap_account
);
804 buf
= salloc(bufsz
= strlen(cachedir
) + strlen(eaccount
) + 2);
805 snprintf(buf
, bufsz
, "%s/%s", cachedir
, eaccount
);
806 if ((dirp
= opendir(buf
)) == NULL
)
808 oldbox
= mp
->mb_imap_mailbox
;
809 while ((dp
= readdir(dirp
)) != NULL
) {
810 if (dp
->d_name
[0] == '.')
812 mp
->mb_imap_mailbox
= urlxdec(dp
->d_name
);
816 mp
->mb_imap_mailbox
= oldbox
;
823 dequeue1(struct mailbox
*mp
)
825 FILE *fp
= NULL
, *uvfp
= NULL
;
826 char *qname
, *uvname
;
833 fp
= cache_queue1(mp
, "r+", &qname
);
834 if (fp
!= NULL
&& fsize(fp
) > 0) {
835 if (imap_select(mp
, &is_size
, &is_count
, mp
->mb_imap_mailbox
) != OKAY
) {
836 fprintf(stderr
, "Cannot select \"%s\" for dequeuing.\n",
837 mp
->mb_imap_mailbox
);
840 if ((uvname
= encname(mp
, "UIDVALIDITY", 0, NULL
)) == NULL
||
841 (uvfp
= Fopen(uvname
, "r")) == NULL
||
842 (fcntl_lock(fileno(uvfp
), FLOCK_READ
), 0) ||
843 fscanf(uvfp
, "%lu", &uv
) != 1 || uv
!= mp
->mb_uidvalidity
) {
844 fprintf(stderr
, "Unique identifiers for \"%s\" are out of date. "
845 "Cannot commit IMAP commands.\n", mp
->mb_imap_mailbox
);
847 fputs("Saving IMAP commands to dead.letter\n", stderr
);
848 savedeadletter(fp
, 0);
849 ftruncate(fileno(fp
), 0);
857 printf("Committing IMAP commands for \"%s\"\n", mp
->mb_imap_mailbox
);
858 imap_dequeue(mp
, fp
);
868 #endif /* HAVE_IMAP */
870 /* vim:set fenc=utf-8:s-it-mode */