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
,
60 static enum okay
dequeue1(struct mailbox
*mp
);
62 static const char infofmt
[] = "%c %lu %d %lu %ld";
65 ((f) & (MSAVED|MDELETED|MREAD|MBOXED|MNEW|MFLAGGED|MANSWERED|MDRAFTED))
67 static const char README1
[] = "\
68 This is a cache directory maintained by " UAGENT
"(1).\n\
69 You should not change any files within.\n\
70 Nevertheless, the structure is as follows: Each subdirectory of the\n\
71 current directory represents an IMAP account, and each subdirectory\n\
72 below that represents a mailbox. Each mailbox directory contains a file\n\
73 named UIDVALIDITY which describes the validity in relation to the version\n\
74 on the server. Other files have names corresponding to their IMAP UID.\n";
75 static const char README2
[] = "\n\
76 The first 128 bytes of these files are used to store message attributes; the\n\
77 following data is equivalent to compress(1) output. So if you have to save a\n\
78 message by hand because of an emergency, throw away the first 128 bytes and\n\
79 decompress the rest, as e.g. 'dd if=MESSAGEFILE skip=1 bs=128 | zcat' does.\n";
80 static const char README3
[] = "\n\
81 Files named QUEUE contain data that will be sent do the IMAP server next\n\
82 time a connection is made in online mode.\n";
83 static const char README4
[] = "\n\
84 You can safely delete any file or directory here, unless it contains a QUEUE\n\
85 file that is not empty; " UAGENT
86 "mailx(1) will download the data again and will also\n\
87 write new cache entries if configured in this way. If you do not wish to use\n\
88 the cache anymore, delete the entire directory and unset the 'imap-cache'\n\
89 variable in " UAGENT
"(1).\n";
90 static const char README5
[] = "\n\
91 For more information about " UAGENT
"(1), visit\n\
92 <http://sdaoden.users.sourceforge.net/code.html>.\n"; /* TODO MAGIC CONSTANT */
95 encname(struct mailbox
*mp
, const char *name
, int same
, const char *box
)
97 char *cachedir
, *eaccount
, *ename
, *res
;
102 ename
= urlxenc(name
);
103 if (mp
->mb_cache_directory
&& same
&& box
== NULL
) {
104 res
= salloc(resz
= strlen(mp
->mb_cache_directory
) + strlen(ename
) + 2);
105 snprintf(res
, resz
, "%s%s%s", mp
->mb_cache_directory
,
106 (*ename
? "/" : ""), ename
);
108 if ((cachedir
= ok_vlook(imap_cache
)) == NULL
||
109 (cachedir
= file_expand(cachedir
)) == NULL
) {
113 eaccount
= urlxenc(mp
->mb_imap_account
);
115 emailbox
= urlxenc(box
);
116 else if (asccasecmp(mp
->mb_imap_mailbox
, "INBOX"))
117 emailbox
= urlxenc(mp
->mb_imap_mailbox
);
120 res
= salloc(resz
= strlen(cachedir
) + strlen(eaccount
) +
121 strlen(emailbox
) + strlen(ename
) + 4);
122 snprintf(res
, resz
, "%s/%s/%s%s%s", cachedir
, eaccount
, emailbox
,
123 (*ename
? "/" : ""), ename
);
131 encuid(struct mailbox
*mp
, unsigned long uid
)
136 snprintf(buf
, sizeof buf
, "%lu", uid
);
137 cp
= encname(mp
, buf
, 1, NULL
);
143 getcache1(struct mailbox
*mp
, struct message
*m
, enum needspec need
,
147 long n
= 0, size
= 0, xsize
, xtime
, xlines
= -1, lines
= 0;
148 int lastc
= EOF
, i
, xflag
, inheader
= 1;
155 if (setflags
== 0 && ((mp
->mb_type
!= MB_IMAP
&& mp
->mb_type
!= MB_CACHE
) ||
158 if ((fp
= Fopen(encuid(mp
, m
->m_uid
), "r")) == NULL
)
161 (void)fcntl_lock(fileno(fp
), F_RDLCK
);
162 if (fscanf(fp
, infofmt
, &b
, (unsigned long*)&xsize
, &xflag
,
163 (unsigned long*)&xtime
, &xlines
) < 4)
165 if (need
!= NEED_UNSPEC
) {
168 if (need
== NEED_HEADER
)
172 if (need
== NEED_HEADER
|| need
== NEED_BODY
)
182 if (fseek(fp
, INITSKIP
, SEEK_SET
) < 0)
185 if (fseek(mp
->mb_otf
, 0L, SEEK_END
) < 0) {
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
))
217 m
->m_block
= mailx_blockof(offset
);
218 m
->m_offset
= mailx_offsetof(offset
);
224 m
->m_flag
= xflag
| MNOFROM
;
226 m
->m_flag
|= MHIDDEN
;
229 if (xlines
> 0 && m
->m_xlines
<= 0)
230 m
->m_xlines
= xlines
;
234 if (xflag
== MREAD
&& xlines
> 0)
235 m
->m_flag
|= MFULLYCACHED
;
236 if (need
== NEED_BODY
) {
237 m
->m_have
|= HAVE_HEADER
| HAVE_BODY
;
239 m
->m_xlines
= m
->m_lines
;
244 m
->m_have
|= HAVE_HEADER
;
258 getcache(struct mailbox
*mp
, struct message
*m
, enum needspec need
)
263 rv
= getcache1(mp
, m
, need
, 0);
269 putcache(struct mailbox
*mp
, struct message
*m
)
271 char iob
[32768], *name
, ob
;
274 long n
, cnt
, oldoffset
, osize
, otime
, olines
= -1;
278 if ((mp
->mb_type
!= MB_IMAP
&& mp
->mb_type
!= MB_CACHE
) || m
->m_uid
== 0 ||
279 m
->m_time
== 0 || (m
->m_flag
& (MTOUCH
|MFULLYCACHED
)) == MFULLYCACHED
)
281 if (m
->m_have
& HAVE_BODY
)
283 else if (m
->m_have
& HAVE_HEADER
)
285 else if (m
->m_have
== HAVE_NOTHING
)
289 if ((oldoffset
= ftell(mp
->mb_itf
)) < 0) /* XXX weird err hdling */
291 if ((obuf
= Fopen(name
= encuid(mp
, m
->m_uid
), "r+")) == NULL
) {
292 if ((obuf
= Fopen(name
, "w")) == NULL
)
294 (void)fcntl_lock(fileno(obuf
), F_WRLCK
); /* XXX err hdl */
296 (void)fcntl_lock(fileno(obuf
), F_WRLCK
); /* XXX err hdl */
297 if (fscanf(obuf
, infofmt
, &ob
, (unsigned long*)&osize
, &oflag
,
298 (unsigned long*)&otime
, &olines
) >= 4 && ob
!= '\0' &&
299 (ob
== 'B' || (ob
== 'H' && c
!= 'B'))) {
300 if (m
->m_xlines
<= 0 && olines
> 0)
301 m
->m_xlines
= olines
;
302 if ((c
!= 'N' && (size_t)osize
!= m
->m_xsize
) ||
303 oflag
!= (int)USEBITS(m
->m_flag
) || otime
!= m
->m_time
||
304 (m
->m_xlines
> 0 && olines
!= m
->m_xlines
)) {
307 fprintf(obuf
, infofmt
, ob
, (unsigned long)m
->m_xsize
,
308 USEBITS(m
->m_flag
), (unsigned long)m
->m_time
, m
->m_xlines
);
316 ftruncate(fileno(obuf
), 0);
318 if ((ibuf
= setinput(mp
, m
, NEED_UNSPEC
)) == NULL
) {
324 fseek(obuf
, INITSKIP
, SEEK_SET
);
328 n
= (cnt
> (long)sizeof iob
) ? (long)sizeof iob
: cnt
;
330 if ((size_t)n
!= fread(iob
, 1, n
, ibuf
) ||
331 n
!= (long)zwrite(zp
, iob
, n
)) {
343 fprintf(obuf
, infofmt
, c
, (unsigned long)m
->m_xsize
, USEBITS(m
->m_flag
),
344 (unsigned long)m
->m_time
, m
->m_xlines
);
350 if (c
== 'B' && USEBITS(m
->m_flag
) == MREAD
)
351 m
->m_flag
|= MFULLYCACHED
;
353 if (Fclose(obuf
) != 0) {
354 m
->m_flag
&= ~MFULLYCACHED
;
357 (void)fseek(mp
->mb_itf
, oldoffset
, SEEK_SET
);
363 initcache(struct mailbox
*mp
)
371 if (mp
->mb_cache_directory
!= NULL
)
372 free(mp
->mb_cache_directory
);
373 mp
->mb_cache_directory
= NULL
;
374 if ((name
= encname(mp
, "", 1, NULL
)) == NULL
)
376 mp
->mb_cache_directory
= sstrdup(name
);
377 if ((uvname
= encname(mp
, "UIDVALIDITY", 1, NULL
)) == NULL
)
379 if (cwget(&cw
) == STOP
)
381 if ((uvfp
= Fopen(uvname
, "r+")) == NULL
||
382 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
383 fscanf(uvfp
, "%lu", &uv
) != 1 || uv
!= mp
->mb_uidvalidity
) {
384 if ((uvfp
= clean(mp
, &cw
)) == NULL
)
390 fcntl_lock(fileno(uvfp
), F_WRLCK
);
391 fprintf(uvfp
, "%lu\n", mp
->mb_uidvalidity
);
392 if (ferror(uvfp
) || Fclose(uvfp
) != 0) {
394 mp
->mb_uidvalidity
= 0;
403 purgecache(struct mailbox
*mp
, struct message
*m
, long mc
)
409 if ((name
= encname(mp
, "", 1, NULL
)) == NULL
)
411 if (cwget(&cw
) == STOP
)
413 purge(mp
, m
, mc
, &cw
, name
);
420 clean(struct mailbox
*mp
, struct cw
*cw
)
422 char *cachedir
, *eaccount
, *buf
;
423 char const *emailbox
;
430 if ((cachedir
= ok_vlook(imap_cache
)) == NULL
||
431 (cachedir
= file_expand(cachedir
)) == NULL
)
433 eaccount
= urlxenc(mp
->mb_imap_account
);
434 if (asccasecmp(mp
->mb_imap_mailbox
, "INBOX"))
435 emailbox
= urlxenc(mp
->mb_imap_mailbox
);
438 buf
= salloc(bufsz
= strlen(cachedir
) + strlen(eaccount
) +
439 strlen(emailbox
) + 40);
440 if (makedir(cachedir
) != OKAY
)
442 snprintf(buf
, bufsz
, "%s/README", cachedir
);
443 if ((fp
= Fopen(buf
, "wx")) != NULL
) {
452 snprintf(buf
, bufsz
, "%s/%s", cachedir
, eaccount
);
453 if (makedir(buf
) != OKAY
)
455 snprintf(buf
, bufsz
, "%s/%s/%s", cachedir
, eaccount
, emailbox
);
456 if (makedir(buf
) != OKAY
)
460 if ((dirp
= opendir(".")) == NULL
)
462 while ((dp
= readdir(dirp
)) != NULL
) {
463 if (dp
->d_name
[0] == '.' && (dp
->d_name
[1] == '\0' ||
464 (dp
->d_name
[1] == '.' && dp
->d_name
[2] == '\0')))
469 fp
= Fopen("UIDVALIDITY", "w");
471 if (cwret(cw
) == STOP
) {
472 fputs("Fatal: Cannot change back to current directory.\n", stderr
);
480 static unsigned long *
481 builds(long *contentelem
)
483 unsigned long n
, *contents
= NULL
;
484 long contentalloc
= 0;
491 if ((dirp
= opendir(".")) == NULL
)
493 while ((dp
= readdir(dirp
)) != NULL
) {
494 if (dp
->d_name
[0] == '.' && (dp
->d_name
[1] == '\0' ||
495 (dp
->d_name
[1] == '.' && dp
->d_name
[2] == '\0')))
497 n
= strtoul(dp
->d_name
, &x
, 10);
500 if (*contentelem
>= contentalloc
- 1)
501 contents
= srealloc(contents
,
502 (contentalloc
+= 200) * sizeof *contents
);
503 contents
[(*contentelem
)++] = n
;
506 if (*contentelem
> 0) {
507 contents
[*contentelem
] = 0;
508 qsort(contents
, *contentelem
, sizeof *contents
, longlt
);
516 purge(struct mailbox
*mp
, struct message
*m
, long mc
, struct cw
*cw
,
519 unsigned long *contents
;
520 long i
, j
, contentelem
;
526 contents
= builds(&contentelem
);
529 while (j
< contentelem
) {
530 if (i
< mc
&& m
[i
].m_uid
== contents
[j
]) {
533 } else if (i
< mc
&& m
[i
].m_uid
< contents
[j
])
536 remve(contents
[j
++]);
539 if (cwret(cw
) == STOP
) {
540 fputs("Fatal: Cannot change back to current directory.\n", stderr
);
549 longlt(const void *a
, const void *b
)
551 union {long l
; int i
;} u
;
554 u
.l
= *(long const*)a
- *(long const*)b
;
555 u
.i
= (u
.l
< 0) ? -1 : ((u
.l
> 0) ? 1 : 0);
561 remve(unsigned long n
)
566 snprintf(buf
, sizeof buf
, "%lu", n
);
572 delcache(struct mailbox
*mp
, struct message
*m
)
577 fn
= encuid(mp
, m
->m_uid
);
578 if (fn
&& unlink(fn
) == 0)
579 m
->m_flag
|= MUNLINKED
;
584 cache_setptr(int transparent
)
587 int i
, omsgCount
= 0;
589 unsigned long *contents
;
591 struct message
*omessage
= NULL
;
597 omsgCount
= msgCount
;
599 free(mb
.mb_cache_directory
);
600 mb
.mb_cache_directory
= NULL
;
601 if ((name
= encname(&mb
, "", 1, NULL
)) == NULL
)
603 mb
.mb_cache_directory
= sstrdup(name
);
604 if (cwget(&cw
) == STOP
)
608 contents
= builds(&contentelem
);
609 msgCount
= contentelem
;
610 message
= scalloc(msgCount
+ 1, sizeof *message
);
611 if (cwret(&cw
) == STOP
) {
612 fputs("Fatal: Cannot change back to current directory.\n", stderr
);
616 for (i
= 0; i
< msgCount
; i
++) {
617 message
[i
].m_uid
= contents
[i
];
618 getcache1(&mb
, &message
[i
], NEED_UNSPEC
, 3);
620 mb
.mb_type
= MB_CACHE
;
621 mb
.mb_perm
= (options
& OPT_R_FLAG
) ? 0 : MB_DELE
;
623 transflags(omessage
, omsgCount
, 1);
633 cache_list(struct mailbox
*mp
, const char *base
, int strip
, FILE *fp
)
635 char *name
, *cachedir
, *eaccount
;
638 const char *cp
, *bp
, *sp
;
643 if ((cachedir
= ok_vlook(imap_cache
)) == NULL
||
644 (cachedir
= file_expand(cachedir
)) == NULL
)
646 eaccount
= urlxenc(mp
->mb_imap_account
);
647 name
= salloc(namesz
= strlen(cachedir
) + strlen(eaccount
) + 2);
648 snprintf(name
, namesz
, "%s/%s", cachedir
, eaccount
);
649 if ((dirp
= opendir(name
)) == NULL
)
651 while ((dp
= readdir(dirp
)) != NULL
) {
652 if (dp
->d_name
[0] == '.')
654 cp
= sp
= urlxdec(dp
->d_name
);
655 for (bp
= base
; *bp
&& *bp
== *sp
; bp
++)
659 cp
= strip
? sp
: cp
;
660 fprintf(fp
, "%s\n", *cp
? cp
: "INBOX");
670 cache_remove(const char *name
)
676 int pathsize
, pathend
, n
;
680 if ((dir
= encname(&mb
, "", 0, imap_fileof(name
))) == NULL
)
682 pathend
= strlen(dir
);
683 path
= smalloc(pathsize
= pathend
+ 30);
684 memcpy(path
, dir
, pathend
);
685 path
[pathend
++] = '/';
686 path
[pathend
] = '\0';
687 if ((dirp
= opendir(path
)) == NULL
) {
691 while ((dp
= readdir(dirp
)) != NULL
) {
692 if (dp
->d_name
[0] == '.' && (dp
->d_name
[1] == '\0' ||
693 (dp
->d_name
[1] == '.' && dp
->d_name
[2] == '\0')))
695 n
= strlen(dp
->d_name
) + 1;
696 if (pathend
+ n
> pathsize
)
697 path
= srealloc(path
, pathsize
= pathend
+ n
+ 30);
698 memcpy(path
+ pathend
, dp
->d_name
, n
);
699 if (stat(path
, &st
) < 0 || (st
.st_mode
& S_IFMT
) != S_IFREG
)
701 if (unlink(path
) < 0) {
710 path
[pathend
] = '\0';
711 rmdir(path
); /* no error on failure, might contain submailboxes */
719 cache_rename(const char *old
, const char *new)
721 char *olddir
, *newdir
;
725 if ((olddir
= encname(&mb
, "", 0, imap_fileof(old
))) == NULL
||
726 (newdir
= encname(&mb
, "",0, imap_fileof(new))) == NULL
)
728 if (rename(olddir
, newdir
) < 0) {
738 cached_uidvalidity(struct mailbox
*mp
)
745 if ((uvname
= encname(mp
, "UIDVALIDITY", 1, NULL
)) == NULL
) {
749 if ((uvfp
= Fopen(uvname
, "r")) == NULL
||
750 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
751 fscanf(uvfp
, "%lu", &uv
) != 1)
761 cache_queue1(struct mailbox
*mp
, char const *mode
, char **xname
)
767 if ((name
= encname(mp
, "QUEUE", 0, NULL
)) == NULL
)
769 if ((fp
= Fopen(name
, mode
)) != NULL
)
770 fcntl_lock(fileno(fp
), F_WRLCK
);
779 cache_queue(struct mailbox
*mp
)
784 fp
= cache_queue1(mp
, "a", NULL
);
786 fputs("Cannot queue IMAP command. Retry when online.\n", stderr
);
792 cache_dequeue(struct mailbox
*mp
)
795 char *cachedir
, *eaccount
, *buf
, *oldbox
;
801 if ((cachedir
= ok_vlook(imap_cache
)) == NULL
||
802 (cachedir
= file_expand(cachedir
)) == NULL
)
804 eaccount
= urlxenc(mp
->mb_imap_account
);
805 buf
= salloc(bufsz
= strlen(cachedir
) + strlen(eaccount
) + 2);
806 snprintf(buf
, bufsz
, "%s/%s", cachedir
, eaccount
);
807 if ((dirp
= opendir(buf
)) == NULL
)
809 oldbox
= mp
->mb_imap_mailbox
;
810 while ((dp
= readdir(dirp
)) != NULL
) {
811 if (dp
->d_name
[0] == '.')
813 mp
->mb_imap_mailbox
= urlxdec(dp
->d_name
);
817 mp
->mb_imap_mailbox
= oldbox
;
824 dequeue1(struct mailbox
*mp
)
826 FILE *fp
= NULL
, *uvfp
= NULL
;
827 char *qname
, *uvname
;
834 fp
= cache_queue1(mp
, "r+", &qname
);
835 if (fp
!= NULL
&& fsize(fp
) > 0) {
836 if (imap_select(mp
, &is_size
, &is_count
, mp
->mb_imap_mailbox
) != OKAY
) {
837 fprintf(stderr
, "Cannot select \"%s\" for dequeuing.\n",
838 mp
->mb_imap_mailbox
);
841 if ((uvname
= encname(mp
, "UIDVALIDITY", 0, NULL
)) == NULL
||
842 (uvfp
= Fopen(uvname
, "r")) == NULL
||
843 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
844 fscanf(uvfp
, "%lu", &uv
) != 1 || uv
!= mp
->mb_uidvalidity
) {
845 fprintf(stderr
, "Unique identifiers for \"%s\" are out of date. "
846 "Cannot commit IMAP commands.\n", mp
->mb_imap_mailbox
);
848 fputs("Saving IMAP commands to dead.letter\n", stderr
);
849 savedeadletter(fp
, 0);
850 ftruncate(fileno(fp
), 0);
858 printf("Committing IMAP commands for \"%s\"\n", mp
->mb_imap_mailbox
);
859 imap_dequeue(mp
, fp
);
869 #endif /* HAVE_IMAP */
871 /* vim:set fenc=utf-8:s-it-mode */