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 - 2013 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
43 typedef int avoid_empty_file_compiler_warning
;
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
, char **xname
);
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 s-nail(1). You should not change any\n\
69 files within. Nevertheless, the structure is as follows: Each subdirectory\n\
70 of the 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; mailx(1) will download the data again and will also\n\
85 write new cache entries if configured in this way. If you do not wish to use\n\
86 the cache anymore, delete the entire directory and unset the 'imap-cache'\n\
87 variable in mailx(1).\n";
88 static const char README5
[] = "\n\
89 For more information about s-nail(1), visit\n\
90 <http://sdaoden.users.sourceforge.net/code.html>.\n";
93 encname(struct mailbox
*mp
, const char *name
, int same
, const char *box
)
95 char *cachedir
, *eaccount
, *ename
, *res
;
99 ename
= urlxenc(name
);
100 if (mp
->mb_cache_directory
&& same
&& box
== NULL
) {
101 res
= salloc(resz
= strlen(mp
->mb_cache_directory
) +
103 snprintf(res
, resz
, "%s%s%s", mp
->mb_cache_directory
,
104 *ename
? "/" : "", ename
);
106 if ((cachedir
= value("imap-cache")) == NULL
||
107 (cachedir
= file_expand(cachedir
)) == NULL
)
109 eaccount
= urlxenc(mp
->mb_imap_account
);
111 emailbox
= urlxenc(box
);
112 else if (asccasecmp(mp
->mb_imap_mailbox
, "INBOX"))
113 emailbox
= urlxenc(mp
->mb_imap_mailbox
);
116 res
= salloc(resz
= strlen(cachedir
) + strlen(eaccount
) +
117 strlen(emailbox
) + strlen(ename
) + 4);
118 snprintf(res
, resz
, "%s/%s/%s%s%s",
119 cachedir
, eaccount
, emailbox
,
120 *ename
? "/" : "", ename
);
126 encuid(struct mailbox
*mp
, unsigned long uid
)
130 snprintf(buf
, sizeof buf
, "%lu", uid
);
131 return encname(mp
, buf
, 1, NULL
);
135 getcache1(struct mailbox
*mp
, struct message
*m
, enum needspec need
,
139 long n
= 0, size
= 0, xsize
, xtime
, xlines
= -1, lines
= 0;
140 int lastc
= EOF
, i
, xflag
, inheader
= 1;
145 if (setflags
== 0 && ((mp
->mb_type
!= MB_IMAP
&&
146 mp
->mb_type
!= MB_CACHE
) ||
149 if ((fp
= Fopen(encuid(mp
, m
->m_uid
), "r")) == NULL
)
151 (void)fcntl_lock(fileno(fp
), F_RDLCK
);
152 if (fscanf(fp
, infofmt
, &b
, (unsigned long*)&xsize
, &xflag
,
153 (unsigned long*)&xtime
, &xlines
) < 4)
155 if (need
!= NEED_UNSPEC
) {
158 if (need
== NEED_HEADER
)
162 if (need
== NEED_HEADER
|| need
== NEED_BODY
)
172 if (fseek(fp
, INITSKIP
, SEEK_SET
) < 0)
175 if (fseek(mp
->mb_otf
, 0L, SEEK_END
) < 0) {
179 offset
= ftell(mp
->mb_otf
);
180 while (inheader
&& (n
= zread(zp
, iob
, sizeof iob
)) > 0) {
182 for (i
= 0; i
< n
; i
++) {
183 if (iob
[i
] == '\n') {
190 fwrite(iob
, 1, n
, mp
->mb_otf
);
192 if (n
> 0 && need
== NEED_BODY
) {
193 while ((n
= zread(zp
, iob
, sizeof iob
)) > 0) {
195 for (i
= 0; i
< n
; i
++)
198 fwrite(iob
, 1, n
, mp
->mb_otf
);
202 if (zfree(zp
) < 0 || n
< 0 || ferror(fp
) || ferror(mp
->mb_otf
))
206 m
->m_block
= mailx_blockof(offset
);
207 m
->m_offset
= mailx_offsetof(offset
);
208 flags
: if (setflags
) {
212 m
->m_flag
= xflag
| MNOFROM
;
214 m
->m_flag
|= MHIDDEN
;
217 if (xlines
> 0 && m
->m_xlines
<= 0)
218 m
->m_xlines
= xlines
;
222 if (xflag
== MREAD
&& xlines
> 0)
223 m
->m_flag
|= MFULLYCACHED
;
224 if (need
== NEED_BODY
) {
225 m
->m_have
|= HAVE_HEADER
|HAVE_BODY
;
227 m
->m_xlines
= m
->m_lines
;
232 m
->m_have
|= HAVE_HEADER
;
245 getcache(struct mailbox
*mp
, struct message
*m
, enum needspec need
)
247 return getcache1(mp
, m
, need
, 0);
251 putcache(struct mailbox
*mp
, struct message
*m
)
256 long n
, cnt
, oldoffset
, osize
, otime
, olines
= -1;
260 if ((mp
->mb_type
!= MB_IMAP
&& mp
->mb_type
!= MB_CACHE
) ||
261 m
->m_uid
== 0 || m
->m_time
== 0 ||
262 (m
->m_flag
& (MTOUCH
|MFULLYCACHED
)) == MFULLYCACHED
)
264 if (m
->m_have
& HAVE_BODY
)
266 else if (m
->m_have
& HAVE_HEADER
)
268 else if (m
->m_have
== HAVE_NOTHING
)
272 if ((oldoffset
= ftell(mp
->mb_itf
)) < 0) /* XXX weird err hdling */
274 if ((obuf
= Fopen(name
= encuid(mp
, m
->m_uid
), "r+")) == NULL
) {
275 if ((obuf
= Fopen(name
, "w")) == NULL
)
277 (void)fcntl_lock(fileno(obuf
), F_WRLCK
); /* XXX err hdl */
279 (void)fcntl_lock(fileno(obuf
), F_WRLCK
); /* XXX err hdl */
280 if (fscanf(obuf
, infofmt
, &ob
, (unsigned long*)&osize
, &oflag
,
281 (unsigned long*)&otime
, &olines
) >= 4 &&
282 ob
!= '\0' && (ob
== 'B' ||
283 (ob
== 'H' && c
!= 'B'))) {
284 if (m
->m_xlines
<= 0 && olines
> 0)
285 m
->m_xlines
= olines
;
286 if ((c
!= 'N' && (size_t)osize
!= m
->m_xsize
) ||
287 oflag
!= (int)USEBITS(m
->m_flag
) ||
288 otime
!= m
->m_time
||
290 olines
!= m
->m_xlines
)) {
293 fprintf(obuf
, infofmt
, ob
,
294 (unsigned long)m
->m_xsize
,
296 (unsigned long)m
->m_time
,
305 ftruncate(fileno(obuf
), 0);
307 if ((ibuf
= setinput(mp
, m
, NEED_UNSPEC
)) == NULL
) {
313 fseek(obuf
, INITSKIP
, SEEK_SET
);
317 n
= cnt
> (long)sizeof iob
? (long)sizeof iob
: cnt
;
319 if ((size_t)n
!= fread(iob
, 1, n
, ibuf
) ||
320 n
!= (long)zwrite(zp
, iob
, n
)) {
331 fprintf(obuf
, infofmt
, c
, (unsigned long)m
->m_xsize
,
333 (unsigned long)m
->m_time
,
340 if (c
== 'B' && USEBITS(m
->m_flag
) == MREAD
)
341 m
->m_flag
|= MFULLYCACHED
;
342 out
: if (Fclose(obuf
) != 0) {
343 m
->m_flag
&= ~MFULLYCACHED
;
346 (void)fseek(mp
->mb_itf
, oldoffset
, SEEK_SET
);
350 initcache(struct mailbox
*mp
)
357 if (mp
->mb_cache_directory
!= NULL
)
358 free(mp
->mb_cache_directory
);
359 mp
->mb_cache_directory
= NULL
;
360 if ((name
= encname(mp
, "", 1, NULL
)) == NULL
)
362 mp
->mb_cache_directory
= sstrdup(name
);
363 if ((uvname
= encname(mp
, "UIDVALIDITY", 1, NULL
)) == NULL
)
365 if (cwget(&cw
) == STOP
)
367 if ((uvfp
= Fopen(uvname
, "r+")) == NULL
||
368 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
369 fscanf(uvfp
, "%lu", &uv
) != 1 ||
370 uv
!= mp
->mb_uidvalidity
) {
371 if ((uvfp
= clean(mp
, &cw
)) == NULL
)
377 fcntl_lock(fileno(uvfp
), F_WRLCK
);
378 fprintf(uvfp
, "%lu\n", mp
->mb_uidvalidity
);
379 if (ferror(uvfp
) || Fclose(uvfp
) != 0) {
381 mp
->mb_uidvalidity
= 0;
387 purgecache(struct mailbox
*mp
, struct message
*m
, long mc
)
392 if ((name
= encname(mp
, "", 1, NULL
)) == NULL
)
394 if (cwget(&cw
) == STOP
)
396 purge(mp
, m
, mc
, &cw
, name
);
401 clean(struct mailbox
*mp
, struct cw
*cw
)
403 char *cachedir
, *eaccount
, *buf
;
404 char const *emailbox
;
410 if ((cachedir
= value("imap-cache")) == NULL
||
411 (cachedir
= file_expand(cachedir
)) == NULL
)
413 eaccount
= urlxenc(mp
->mb_imap_account
);
414 if (asccasecmp(mp
->mb_imap_mailbox
, "INBOX"))
415 emailbox
= urlxenc(mp
->mb_imap_mailbox
);
418 buf
= salloc(bufsz
= strlen(cachedir
) + strlen(eaccount
) +
419 strlen(emailbox
) + 40);
420 if (makedir(cachedir
) != OKAY
)
422 snprintf(buf
, bufsz
, "%s/README", cachedir
);
423 if ((fp
= Fopen(buf
, "wx")) != NULL
) {
431 snprintf(buf
, bufsz
, "%s/%s", cachedir
, eaccount
);
432 if (makedir(buf
) != OKAY
)
434 snprintf(buf
, bufsz
, "%s/%s/%s", cachedir
, eaccount
, emailbox
);
435 if (makedir(buf
) != OKAY
)
439 if ((dirp
= opendir(".")) == NULL
)
441 while ((dp
= readdir(dirp
)) != NULL
) {
442 if (dp
->d_name
[0] == '.' &&
443 (dp
->d_name
[1] == '\0' ||
444 (dp
->d_name
[1] == '.' &&
445 dp
->d_name
[2] == '\0')))
450 fp
= Fopen("UIDVALIDITY", "w");
451 out
: if (cwret(cw
) == STOP
) {
452 fputs("Fatal: Cannot change back to current directory.\n",
459 static unsigned long *
460 builds(long *contentelem
)
462 unsigned long n
, *contents
= NULL
;
463 long contentalloc
= 0;
469 if ((dirp
= opendir(".")) == NULL
)
471 while ((dp
= readdir(dirp
)) != NULL
) {
472 if (dp
->d_name
[0] == '.' &&
473 (dp
->d_name
[1] == '\0' ||
474 (dp
->d_name
[1] == '.' &&
475 dp
->d_name
[2] == '\0')))
477 n
= strtoul(dp
->d_name
, &x
, 10);
480 if (*contentelem
>= contentalloc
- 1)
481 contents
= srealloc(contents
,
482 (contentalloc
+= 200) * sizeof *contents
);
483 contents
[(*contentelem
)++] = n
;
486 if (*contentelem
> 0) {
487 contents
[*contentelem
] = 0;
488 qsort(contents
, *contentelem
, sizeof *contents
, longlt
);
494 purge(struct mailbox
*mp
, struct message
*m
, long mc
, struct cw
*cw
,
497 unsigned long *contents
;
498 long i
, j
, contentelem
;
503 contents
= builds(&contentelem
);
506 while (j
< contentelem
) {
507 if (i
< mc
&& m
[i
].m_uid
== contents
[j
]) {
510 } else if (i
< mc
&& m
[i
].m_uid
< contents
[j
])
513 remve(contents
[j
++]);
516 if (cwret(cw
) == STOP
) {
517 fputs("Fatal: Cannot change back to current directory.\n",
525 longlt(const void *a
, const void *b
)
527 return *(const long*)a
- *(const long*)b
;
531 remve(unsigned long n
)
535 snprintf(buf
, sizeof buf
, "%lu", n
);
540 delcache(struct mailbox
*mp
, struct message
*m
)
544 fn
= encuid(mp
, m
->m_uid
);
545 if (fn
&& unlink(fn
) == 0)
546 m
->m_flag
|= MUNLINKED
;
550 cache_setptr(int transparent
)
555 unsigned long *contents
;
558 struct message
*omessage
= NULL
;
563 omsgCount
= msgCount
;
565 free(mb
.mb_cache_directory
);
566 mb
.mb_cache_directory
= NULL
;
567 if ((name
= encname(&mb
, "", 1, NULL
)) == NULL
)
569 mb
.mb_cache_directory
= sstrdup(name
);
570 if (cwget(&cw
) == STOP
)
574 contents
= builds(&contentelem
);
575 msgCount
= contentelem
;
576 message
= scalloc(msgCount
+ 1, sizeof *message
);
577 if (cwret(&cw
) == STOP
) {
578 fputs("Fatal: Cannot change back to current directory.\n",
583 for (i
= 0; i
< msgCount
; i
++) {
584 message
[i
].m_uid
= contents
[i
];
585 getcache1(&mb
, &message
[i
], NEED_UNSPEC
, 3);
589 mb
.mb_type
= MB_CACHE
;
590 mb
.mb_perm
= (options
& OPT_R_FLAG
) ? 0 : MB_DELE
;
592 transflags(omessage
, omsgCount
, 1);
600 cache_list(struct mailbox
*mp
, const char *base
, int strip
, FILE *fp
)
602 char *name
, *cachedir
, *eaccount
;
605 const char *cp
, *bp
, *sp
;
608 if ((cachedir
= value("imap-cache")) == NULL
||
609 (cachedir
= file_expand(cachedir
)) == NULL
)
611 eaccount
= urlxenc(mp
->mb_imap_account
);
612 name
= salloc(namesz
= strlen(cachedir
) + strlen(eaccount
) + 2);
613 snprintf(name
, namesz
, "%s/%s", cachedir
, eaccount
);
614 if ((dirp
= opendir(name
)) == NULL
)
616 while ((dp
= readdir(dirp
)) != NULL
) {
617 if (dp
->d_name
[0] == '.')
619 cp
= sp
= urlxdec(dp
->d_name
);
620 for (bp
= base
; *bp
&& *bp
== *sp
; bp
++)
624 cp
= strip
? sp
: cp
;
625 fprintf(fp
, "%s\n", *cp
? cp
: "INBOX");
632 cache_remove(const char *name
)
638 int pathsize
, pathend
, n
;
641 if ((dir
= encname(&mb
, "", 0, imap_fileof(name
))) == NULL
)
643 pathend
= strlen(dir
);
644 path
= smalloc(pathsize
= pathend
+ 30);
645 memcpy(path
, dir
, pathend
);
646 path
[pathend
++] = '/';
647 path
[pathend
] = '\0';
648 if ((dirp
= opendir(path
)) == NULL
) {
652 while ((dp
= readdir(dirp
)) != NULL
) {
653 if (dp
->d_name
[0] == '.' &&
654 (dp
->d_name
[1] == '\0' ||
655 (dp
->d_name
[1] == '.' &&
656 dp
->d_name
[2] == '\0')))
658 n
= strlen(dp
->d_name
) + 1;
659 if (pathend
+ n
> pathsize
)
660 path
= srealloc(path
, pathsize
= pathend
+ n
+ 30);
661 memcpy(path
+ pathend
, dp
->d_name
, n
);
662 if (stat(path
, &st
) < 0 || (st
.st_mode
&S_IFMT
) != S_IFREG
)
664 if (unlink(path
) < 0) {
672 path
[pathend
] = '\0';
673 rmdir(path
); /* no error on failure, might contain submailboxes */
679 cache_rename(const char *old
, const char *new)
681 char *olddir
, *newdir
;
683 if ((olddir
= encname(&mb
, "", 0, imap_fileof(old
))) == NULL
||
684 (newdir
= encname(&mb
, "",0, imap_fileof(new))) == NULL
)
686 if (rename(olddir
, newdir
) < 0) {
694 cached_uidvalidity(struct mailbox
*mp
)
700 if ((uvname
= encname(mp
, "UIDVALIDITY", 1, NULL
)) == NULL
)
702 if ((uvfp
= Fopen(uvname
, "r")) == NULL
||
703 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
704 fscanf(uvfp
, "%lu", &uv
) != 1)
712 cache_queue1(struct mailbox
*mp
, char const *mode
, char **xname
)
717 if ((name
= encname(mp
, "QUEUE", 0, NULL
)) == NULL
)
719 if ((fp
= Fopen(name
, mode
)) != NULL
)
720 fcntl_lock(fileno(fp
), F_WRLCK
);
727 cache_queue(struct mailbox
*mp
)
731 fp
= cache_queue1(mp
, "a", NULL
);
733 fputs("Cannot queue IMAP command. Retry when online.\n",
739 cache_dequeue(struct mailbox
*mp
)
742 char *cachedir
, *eaccount
, *buf
, *oldbox
;
746 if ((cachedir
= value("imap-cache")) == NULL
||
747 (cachedir
= file_expand(cachedir
)) == NULL
)
749 eaccount
= urlxenc(mp
->mb_imap_account
);
750 buf
= salloc(bufsz
= strlen(cachedir
) + strlen(eaccount
) + 2);
751 snprintf(buf
, bufsz
, "%s/%s", cachedir
, eaccount
);
752 if ((dirp
= opendir(buf
)) == NULL
)
754 oldbox
= mp
->mb_imap_mailbox
;
755 while ((dp
= readdir(dirp
)) != NULL
) {
756 if (dp
->d_name
[0] == '.')
758 mp
->mb_imap_mailbox
= urlxdec(dp
->d_name
);
762 mp
->mb_imap_mailbox
= oldbox
;
767 dequeue1(struct mailbox
*mp
)
769 FILE *fp
= NULL
, *uvfp
= NULL
;
770 char *qname
, *uvname
;
775 fp
= cache_queue1(mp
, "r+", &qname
);
776 if (fp
!= NULL
&& fsize(fp
) > 0) {
777 if (imap_select(mp
, &is_size
, &is_count
,
778 mp
->mb_imap_mailbox
) != OKAY
) {
779 fprintf(stderr
, "Cannot select \"%s\" for dequeuing.\n",
780 mp
->mb_imap_mailbox
);
783 if ((uvname
= encname(mp
, "UIDVALIDITY", 0, NULL
)) == NULL
||
784 (uvfp
= Fopen(uvname
, "r")) == NULL
||
785 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
786 fscanf(uvfp
, "%lu", &uv
) != 1 ||
787 uv
!= mp
->mb_uidvalidity
) {
789 "Unique identifiers for \"%s\" are out of date. "
790 "Cannot commit IMAP commands.\n",
791 mp
->mb_imap_mailbox
);
792 save
: fputs("Saving IMAP commands to dead.letter\n", stderr
);
793 savedeadletter(fp
, 0);
794 ftruncate(fileno(fp
), 0);
801 printf("Committing IMAP commands for \"%s\"\n",
802 mp
->mb_imap_mailbox
);
803 imap_dequeue(mp
, fp
);
811 #endif /* HAVE_IMAP */