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 - 2015 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 #define n_FILE imap_cache
42 #ifndef HAVE_AMALGAMATION
50 static char * encname(struct mailbox
*mp
, const char *name
, int same
,
52 static char * encuid(struct mailbox
*mp
, unsigned long uid
);
53 static FILE * clean(struct mailbox
*mp
, struct cw
*cw
);
54 static unsigned long * builds(long *contentelem
);
55 static void purge(struct mailbox
*mp
, struct message
*m
, long mc
,
56 struct cw
*cw
, const char *name
);
57 static int longlt(const void *a
, const void *b
);
58 static void remve(unsigned long n
);
59 static FILE * cache_queue1(struct mailbox
*mp
, char const *mode
,
61 static enum okay
dequeue1(struct mailbox
*mp
);
63 static const char infofmt
[] = "%c %lu %d %lu %ld";
66 ((f) & (MSAVED|MDELETED|MREAD|MBOXED|MNEW|MFLAGGED|MANSWERED|MDRAFTED))
68 static const char README1
[] = "\
69 This is a cache directory maintained by " UAGENT
"(1).\n\
70 You should not change any files within.\n\
71 Nevertheless, the structure is as follows: Each subdirectory of the\n\
72 current directory represents an IMAP account, and each subdirectory\n\
73 below that represents a mailbox. Each mailbox directory contains a file\n\
74 named UIDVALIDITY which describes the validity in relation to the version\n\
75 on the server. Other files have names corresponding to their IMAP UID.\n";
76 static const char README2
[] = "\n\
77 The first 128 bytes of these files are used to store message attributes; the\n\
78 following data is equivalent to compress(1) output. So if you have to save a\n\
79 message by hand because of an emergency, throw away the first 128 bytes and\n\
80 decompress the rest, as e.g. 'dd if=MESSAGEFILE skip=1 bs=128 | zcat' does.\n";
81 static const char README3
[] = "\n\
82 Files named QUEUE contain data that will be sent do the IMAP server next\n\
83 time a connection is made in online mode.\n";
84 static const char README4
[] = "\n\
85 You can safely delete any file or directory here, unless it contains a QUEUE\n\
86 file that is not empty; " UAGENT
" 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
, TRU1
);
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
, TRU1
);
115 emailbox
= urlxenc(box
, TRU1
);
116 else if (asccasecmp(mp
->mb_imap_mailbox
, "INBOX"))
117 emailbox
= urlxenc(mp
->mb_imap_mailbox
, TRU1
);
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 file_lock(fileno(fp
), FLT_READ
, 0,0, 0);
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 file_lock(fileno(obuf
), FLT_WRITE
, 0,0, 0); /* XXX err hdl */
296 file_lock(fileno(obuf
), FLT_READ
, 0,0, 0); /* 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 (file_lock(fileno(uvfp
), FLT_READ
, 0,0, 0), 0) ||
383 fscanf(uvfp
, "%lu", &uv
) != 1 || uv
!= mp
->mb_uidvalidity
) {
384 if ((uvfp
= clean(mp
, &cw
)) == NULL
)
390 file_lock(fileno(uvfp
), FLT_WRITE
, 0,0, 0);
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
, TRU1
);
434 if (asccasecmp(mp
->mb_imap_mailbox
, "INBOX"))
435 emailbox
= urlxenc(mp
->mb_imap_mailbox
, TRU1
);
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 n_err(_("Fatal: Cannot change back to current directory.\n"));
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
);
527 if (contents
!= NULL
) {
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
++]);
540 if (cwret(cw
) == STOP
) {
541 n_err(_("Fatal: Cannot change back to current directory.\n"));
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(enum fedit_mode fm
, int transparent
)
587 int i
, omsgCount
= 0;
589 unsigned long *contents
;
591 struct message
*omessage
= NULL
;
597 omsgCount
= msgCount
;
599 if (mb
.mb_cache_directory
!= NULL
) {
600 free(mb
.mb_cache_directory
);
601 mb
.mb_cache_directory
= NULL
;
603 if ((name
= encname(&mb
, "", 1, NULL
)) == NULL
)
605 mb
.mb_cache_directory
= sstrdup(name
);
606 if (cwget(&cw
) == STOP
)
610 contents
= builds(&contentelem
);
611 msgCount
= contentelem
;
612 message
= scalloc(msgCount
+ 1, sizeof *message
);
613 if (cwret(&cw
) == STOP
) {
614 n_err(_("Fatal: Cannot change back to current directory.\n"));
620 for (i
= 0; i
< msgCount
; i
++) {
621 message
[i
].m_uid
= contents
[i
];
622 getcache1(&mb
, &message
[i
], NEED_UNSPEC
, 3);
627 if (contents
!= NULL
)
629 mb
.mb_type
= MB_CACHE
;
630 mb
.mb_perm
= ((options
& OPT_R_FLAG
) || (fm
& FEDIT_RDONLY
)) ? 0 : MB_DELE
;
632 transflags(omessage
, omsgCount
, 1);
634 if (omessage
!= NULL
)
645 cache_list(struct mailbox
*mp
, const char *base
, int strip
, FILE *fp
)
647 char *name
, *cachedir
, *eaccount
;
650 const char *cp
, *bp
, *sp
;
655 if ((cachedir
= ok_vlook(imap_cache
)) == NULL
||
656 (cachedir
= file_expand(cachedir
)) == NULL
)
658 eaccount
= urlxenc(mp
->mb_imap_account
, TRU1
);
659 name
= salloc(namesz
= strlen(cachedir
) + strlen(eaccount
) + 2);
660 snprintf(name
, namesz
, "%s/%s", cachedir
, eaccount
);
661 if ((dirp
= opendir(name
)) == NULL
)
663 while ((dp
= readdir(dirp
)) != NULL
) {
664 if (dp
->d_name
[0] == '.')
666 cp
= sp
= urlxdec(dp
->d_name
);
667 for (bp
= base
; *bp
&& *bp
== *sp
; bp
++)
671 cp
= strip
? sp
: cp
;
672 fprintf(fp
, "%s\n", *cp
? cp
: "INBOX");
682 cache_remove(const char *name
)
688 int pathsize
, pathend
, n
;
692 if ((dir
= encname(&mb
, "", 0, imap_fileof(name
))) == NULL
)
694 pathend
= strlen(dir
);
695 path
= smalloc(pathsize
= pathend
+ 30);
696 memcpy(path
, dir
, pathend
);
697 path
[pathend
++] = '/';
698 path
[pathend
] = '\0';
699 if ((dirp
= opendir(path
)) == NULL
) {
703 while ((dp
= readdir(dirp
)) != NULL
) {
704 if (dp
->d_name
[0] == '.' && (dp
->d_name
[1] == '\0' ||
705 (dp
->d_name
[1] == '.' && dp
->d_name
[2] == '\0')))
707 n
= strlen(dp
->d_name
) + 1;
708 if (pathend
+ n
> pathsize
)
709 path
= srealloc(path
, pathsize
= pathend
+ n
+ 30);
710 memcpy(path
+ pathend
, dp
->d_name
, n
);
711 if (stat(path
, &st
) < 0 || (st
.st_mode
& S_IFMT
) != S_IFREG
)
713 if (unlink(path
) < 0) {
722 path
[pathend
] = '\0';
723 rmdir(path
); /* no error on failure, might contain submailboxes */
731 cache_rename(const char *old
, const char *new)
733 char *olddir
, *newdir
;
737 if ((olddir
= encname(&mb
, "", 0, imap_fileof(old
))) == NULL
||
738 (newdir
= encname(&mb
, "",0, imap_fileof(new))) == NULL
)
740 if (rename(olddir
, newdir
) < 0) {
750 cached_uidvalidity(struct mailbox
*mp
)
757 if ((uvname
= encname(mp
, "UIDVALIDITY", 1, NULL
)) == NULL
) {
761 if ((uvfp
= Fopen(uvname
, "r")) == NULL
||
762 (file_lock(fileno(uvfp
), FLT_READ
, 0,0, 0), 0) ||
763 fscanf(uvfp
, "%lu", &uv
) != 1)
773 cache_queue1(struct mailbox
*mp
, char const *mode
, char **xname
)
779 if ((name
= encname(mp
, "QUEUE", 0, NULL
)) == NULL
)
781 if ((fp
= Fopen(name
, mode
)) != NULL
)
782 file_lock(fileno(fp
), FLT_WRITE
, 0,0, 0);
791 cache_queue(struct mailbox
*mp
)
796 fp
= cache_queue1(mp
, "a", NULL
);
798 n_err(_("Cannot queue IMAP command. Retry when online.\n"));
804 cache_dequeue(struct mailbox
*mp
)
807 char *cachedir
, *eaccount
, *buf
, *oldbox
;
813 if ((cachedir
= ok_vlook(imap_cache
)) == NULL
||
814 (cachedir
= file_expand(cachedir
)) == NULL
)
816 eaccount
= urlxenc(mp
->mb_imap_account
, TRU1
);
817 buf
= salloc(bufsz
= strlen(cachedir
) + strlen(eaccount
) + 2);
818 snprintf(buf
, bufsz
, "%s/%s", cachedir
, eaccount
);
819 if ((dirp
= opendir(buf
)) == NULL
)
821 oldbox
= mp
->mb_imap_mailbox
;
822 while ((dp
= readdir(dirp
)) != NULL
) {
823 if (dp
->d_name
[0] == '.')
825 /* FIXME MUST BLOCK SIGNALS IN ORDER TO ENSURE PROPER RESTORE!
826 * (but wuuuuh, what a shit!) */
827 mp
->mb_imap_mailbox
= sstrdup(urlxdec(dp
->d_name
));
829 { char *x
= mp
->mb_imap_mailbox
;
830 mp
->mb_imap_mailbox
= oldbox
;
841 dequeue1(struct mailbox
*mp
)
843 FILE *fp
= NULL
, *uvfp
= NULL
;
844 char *qname
, *uvname
;
851 fp
= cache_queue1(mp
, "r+", &qname
);
852 if (fp
!= NULL
&& fsize(fp
) > 0) {
853 if (imap_select(mp
, &is_size
, &is_count
, mp
->mb_imap_mailbox
, FEDIT_NONE
)
855 n_err(_("Cannot select \"%s\" for dequeuing.\n"), mp
->mb_imap_mailbox
);
858 if ((uvname
= encname(mp
, "UIDVALIDITY", 0, NULL
)) == NULL
||
859 (uvfp
= Fopen(uvname
, "r")) == NULL
||
860 (file_lock(fileno(uvfp
), FLT_READ
, 0,0, 0), 0) ||
861 fscanf(uvfp
, "%lu", &uv
) != 1 || uv
!= mp
->mb_uidvalidity
) {
862 n_err(_("Unique identifiers for \"%s\" are out of date. "
863 "Cannot commit IMAP commands.\n"), mp
->mb_imap_mailbox
);
865 n_err(_("Saving IMAP commands to *DEAD*\n"));
866 savedeadletter(fp
, 0);
867 ftruncate(fileno(fp
), 0);
875 printf("Committing IMAP commands for \"%s\"\n", mp
->mb_imap_mailbox
);
876 imap_dequeue(mp
, fp
);
886 #endif /* HAVE_IMAP */