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
;
57 static char *encname(struct mailbox
*mp
, const char *name
, int same
,
59 static char *encuid(struct mailbox
*mp
, unsigned long uid
);
60 static FILE *clean(struct mailbox
*mp
, struct cw
*cw
);
61 static unsigned long *builds(long *contentelem
);
62 static void purge(struct mailbox
*mp
, struct message
*m
, long mc
,
63 struct cw
*cw
, const char *name
);
64 static int longlt(const void *a
, const void *b
);
65 static void remve(unsigned long n
);
66 static FILE *cache_queue1(struct mailbox
*mp
, char const *mode
, char **xname
);
67 static enum okay
dequeue1(struct mailbox
*mp
);
69 static const char infofmt
[] = "%c %lu %d %lu %ld";
72 ((f) & (MSAVED|MDELETED|MREAD|MBOXED|MNEW|MFLAGGED|MANSWERED|MDRAFTED))
74 static const char README1
[] = "\
75 This is a cache directory maintained by s-nail(1). You should not change any\n\
76 files within. Nevertheless, the structure is as follows: Each subdirectory\n\
77 of the current directory represents an IMAP account, and each subdirectory\n\
78 below that represents a mailbox. Each mailbox directory contains a file\n\
79 named UIDVALIDITY which describes the validity in relation to the version\n\
80 on the server. Other files have names corresponding to their IMAP UID.\n";
81 static const char README2
[] = "\n\
82 The first 128 bytes of these files are used to store message attributes; the\n\
83 following data is equivalent to compress(1) output. So if you have to save a\n\
84 message by hand because of an emergency, throw away the first 128 bytes and\n\
85 decompress the rest, as e.g. 'dd if=MESSAGEFILE skip=1 bs=128 | zcat' does.\n";
86 static const char README3
[] = "\n\
87 Files named QUEUE contain data that will be sent do the IMAP server next\n\
88 time a connection is made in online mode.\n";
89 static const char README4
[] = "\n\
90 You can safely delete any file or directory here, unless it contains a QUEUE\n\
91 file that is not empty; mailx(1) will download the data again and will also\n\
92 write new cache entries if configured in this way. If you do not wish to use\n\
93 the cache anymore, delete the entire directory and unset the 'imap-cache'\n\
94 variable in mailx(1).\n";
95 static const char README5
[] = "\n\
96 For more information about s-nail(1), visit\n\
97 <http://sdaoden.users.sourceforge.net/code.html>.\n";
100 encname(struct mailbox
*mp
, const char *name
, int same
, const char *box
)
102 char *cachedir
, *eaccount
, *ename
, *res
;
103 char const *emailbox
;
106 ename
= urlxenc(name
);
107 if (mp
->mb_cache_directory
&& same
&& box
== NULL
) {
108 res
= salloc(resz
= strlen(mp
->mb_cache_directory
) +
110 snprintf(res
, resz
, "%s%s%s", mp
->mb_cache_directory
,
111 *ename
? "/" : "", ename
);
113 if ((cachedir
= value("imap-cache")) == NULL
||
114 (cachedir
= file_expand(cachedir
)) == NULL
)
116 eaccount
= urlxenc(mp
->mb_imap_account
);
118 emailbox
= urlxenc(box
);
119 else if (asccasecmp(mp
->mb_imap_mailbox
, "INBOX"))
120 emailbox
= urlxenc(mp
->mb_imap_mailbox
);
123 res
= salloc(resz
= strlen(cachedir
) + strlen(eaccount
) +
124 strlen(emailbox
) + strlen(ename
) + 4);
125 snprintf(res
, resz
, "%s/%s/%s%s%s",
126 cachedir
, eaccount
, emailbox
,
127 *ename
? "/" : "", ename
);
133 encuid(struct mailbox
*mp
, unsigned long uid
)
137 snprintf(buf
, sizeof buf
, "%lu", uid
);
138 return 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;
152 if (setflags
== 0 && ((mp
->mb_type
!= MB_IMAP
&&
153 mp
->mb_type
!= MB_CACHE
) ||
156 if ((fp
= Fopen(encuid(mp
, m
->m_uid
), "r")) == NULL
)
158 (void)fcntl_lock(fileno(fp
), F_RDLCK
);
159 if (fscanf(fp
, infofmt
, &b
, (unsigned long*)&xsize
, &xflag
,
160 (unsigned long*)&xtime
, &xlines
) < 4)
162 if (need
!= NEED_UNSPEC
) {
165 if (need
== NEED_HEADER
)
169 if (need
== NEED_HEADER
|| need
== NEED_BODY
)
179 if (fseek(fp
, INITSKIP
, SEEK_SET
) < 0)
182 if (fseek(mp
->mb_otf
, 0L, SEEK_END
) < 0) {
186 offset
= ftell(mp
->mb_otf
);
187 while (inheader
&& (n
= zread(zp
, iob
, sizeof iob
)) > 0) {
189 for (i
= 0; i
< n
; i
++) {
190 if (iob
[i
] == '\n') {
197 fwrite(iob
, 1, n
, mp
->mb_otf
);
199 if (n
> 0 && need
== NEED_BODY
) {
200 while ((n
= zread(zp
, iob
, sizeof iob
)) > 0) {
202 for (i
= 0; i
< n
; i
++)
205 fwrite(iob
, 1, n
, mp
->mb_otf
);
209 if (zfree(zp
) < 0 || n
< 0 || ferror(fp
) || ferror(mp
->mb_otf
))
213 m
->m_block
= mailx_blockof(offset
);
214 m
->m_offset
= mailx_offsetof(offset
);
215 flags
: if (setflags
) {
219 m
->m_flag
= xflag
| MNOFROM
;
221 m
->m_flag
|= MHIDDEN
;
224 if (xlines
> 0 && m
->m_xlines
<= 0)
225 m
->m_xlines
= xlines
;
229 if (xflag
== MREAD
&& xlines
> 0)
230 m
->m_flag
|= MFULLYCACHED
;
231 if (need
== NEED_BODY
) {
232 m
->m_have
|= HAVE_HEADER
|HAVE_BODY
;
234 m
->m_xlines
= m
->m_lines
;
239 m
->m_have
|= HAVE_HEADER
;
252 getcache(struct mailbox
*mp
, struct message
*m
, enum needspec need
)
254 return getcache1(mp
, m
, need
, 0);
258 putcache(struct mailbox
*mp
, struct message
*m
)
263 long n
, cnt
, oldoffset
, osize
, otime
, olines
= -1;
267 if ((mp
->mb_type
!= MB_IMAP
&& mp
->mb_type
!= MB_CACHE
) ||
268 m
->m_uid
== 0 || m
->m_time
== 0 ||
269 (m
->m_flag
& (MTOUCH
|MFULLYCACHED
)) == MFULLYCACHED
)
271 if (m
->m_have
& HAVE_BODY
)
273 else if (m
->m_have
& HAVE_HEADER
)
275 else if (m
->m_have
== HAVE_NOTHING
)
279 if ((oldoffset
= ftell(mp
->mb_itf
)) < 0) /* XXX weird err hdling */
281 if ((obuf
= Fopen(name
= encuid(mp
, m
->m_uid
), "r+")) == NULL
) {
282 if ((obuf
= Fopen(name
, "w")) == NULL
)
284 (void)fcntl_lock(fileno(obuf
), F_WRLCK
); /* XXX err hdl */
286 (void)fcntl_lock(fileno(obuf
), F_WRLCK
); /* XXX err hdl */
287 if (fscanf(obuf
, infofmt
, &ob
, (unsigned long*)&osize
, &oflag
,
288 (unsigned long*)&otime
, &olines
) >= 4 &&
289 ob
!= '\0' && (ob
== 'B' ||
290 (ob
== 'H' && c
!= 'B'))) {
291 if (m
->m_xlines
<= 0 && olines
> 0)
292 m
->m_xlines
= olines
;
293 if ((c
!= 'N' && (size_t)osize
!= m
->m_xsize
) ||
294 oflag
!= (int)USEBITS(m
->m_flag
) ||
295 otime
!= m
->m_time
||
297 olines
!= m
->m_xlines
)) {
300 fprintf(obuf
, infofmt
, ob
,
301 (unsigned long)m
->m_xsize
,
303 (unsigned long)m
->m_time
,
312 ftruncate(fileno(obuf
), 0);
314 if ((ibuf
= setinput(mp
, m
, NEED_UNSPEC
)) == NULL
) {
320 fseek(obuf
, INITSKIP
, SEEK_SET
);
324 n
= cnt
> (long)sizeof iob
? (long)sizeof iob
: cnt
;
326 if ((size_t)n
!= fread(iob
, 1, n
, ibuf
) ||
327 n
!= (long)zwrite(zp
, iob
, n
)) {
338 fprintf(obuf
, infofmt
, c
, (unsigned long)m
->m_xsize
,
340 (unsigned long)m
->m_time
,
347 if (c
== 'B' && USEBITS(m
->m_flag
) == MREAD
)
348 m
->m_flag
|= MFULLYCACHED
;
349 out
: if (Fclose(obuf
) != 0) {
350 m
->m_flag
&= ~MFULLYCACHED
;
353 (void)fseek(mp
->mb_itf
, oldoffset
, SEEK_SET
);
357 initcache(struct mailbox
*mp
)
364 if (mp
->mb_cache_directory
!= NULL
)
365 free(mp
->mb_cache_directory
);
366 mp
->mb_cache_directory
= NULL
;
367 if ((name
= encname(mp
, "", 1, NULL
)) == NULL
)
369 mp
->mb_cache_directory
= sstrdup(name
);
370 if ((uvname
= encname(mp
, "UIDVALIDITY", 1, NULL
)) == NULL
)
372 if (cwget(&cw
) == STOP
)
374 if ((uvfp
= Fopen(uvname
, "r+")) == NULL
||
375 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
376 fscanf(uvfp
, "%lu", &uv
) != 1 ||
377 uv
!= mp
->mb_uidvalidity
) {
378 if ((uvfp
= clean(mp
, &cw
)) == NULL
)
384 fcntl_lock(fileno(uvfp
), F_WRLCK
);
385 fprintf(uvfp
, "%lu\n", mp
->mb_uidvalidity
);
386 if (ferror(uvfp
) || Fclose(uvfp
) != 0) {
388 mp
->mb_uidvalidity
= 0;
394 purgecache(struct mailbox
*mp
, struct message
*m
, long mc
)
399 if ((name
= encname(mp
, "", 1, NULL
)) == NULL
)
401 if (cwget(&cw
) == STOP
)
403 purge(mp
, m
, mc
, &cw
, name
);
408 clean(struct mailbox
*mp
, struct cw
*cw
)
410 char *cachedir
, *eaccount
, *buf
;
411 char const *emailbox
;
417 if ((cachedir
= value("imap-cache")) == NULL
||
418 (cachedir
= file_expand(cachedir
)) == NULL
)
420 eaccount
= urlxenc(mp
->mb_imap_account
);
421 if (asccasecmp(mp
->mb_imap_mailbox
, "INBOX"))
422 emailbox
= urlxenc(mp
->mb_imap_mailbox
);
425 buf
= salloc(bufsz
= strlen(cachedir
) + strlen(eaccount
) +
426 strlen(emailbox
) + 40);
427 if (makedir(cachedir
) != OKAY
)
429 snprintf(buf
, bufsz
, "%s/README", cachedir
);
430 if ((fp
= Fopen(buf
, "wx")) != NULL
) {
438 snprintf(buf
, bufsz
, "%s/%s", cachedir
, eaccount
);
439 if (makedir(buf
) != OKAY
)
441 snprintf(buf
, bufsz
, "%s/%s/%s", cachedir
, eaccount
, emailbox
);
442 if (makedir(buf
) != OKAY
)
446 if ((dirp
= opendir(".")) == NULL
)
448 while ((dp
= readdir(dirp
)) != NULL
) {
449 if (dp
->d_name
[0] == '.' &&
450 (dp
->d_name
[1] == '\0' ||
451 (dp
->d_name
[1] == '.' &&
452 dp
->d_name
[2] == '\0')))
457 fp
= Fopen("UIDVALIDITY", "w");
458 out
: if (cwret(cw
) == STOP
) {
459 fputs("Fatal: Cannot change back to current directory.\n",
466 static unsigned long *
467 builds(long *contentelem
)
469 unsigned long n
, *contents
= NULL
;
470 long contentalloc
= 0;
476 if ((dirp
= opendir(".")) == NULL
)
478 while ((dp
= readdir(dirp
)) != NULL
) {
479 if (dp
->d_name
[0] == '.' &&
480 (dp
->d_name
[1] == '\0' ||
481 (dp
->d_name
[1] == '.' &&
482 dp
->d_name
[2] == '\0')))
484 n
= strtoul(dp
->d_name
, &x
, 10);
487 if (*contentelem
>= contentalloc
- 1)
488 contents
= srealloc(contents
,
489 (contentalloc
+= 200) * sizeof *contents
);
490 contents
[(*contentelem
)++] = n
;
493 if (*contentelem
> 0) {
494 contents
[*contentelem
] = 0;
495 qsort(contents
, *contentelem
, sizeof *contents
, longlt
);
501 purge(struct mailbox
*mp
, struct message
*m
, long mc
, struct cw
*cw
,
504 unsigned long *contents
;
505 long i
, j
, contentelem
;
510 contents
= builds(&contentelem
);
513 while (j
< contentelem
) {
514 if (i
< mc
&& m
[i
].m_uid
== contents
[j
]) {
517 } else if (i
< mc
&& m
[i
].m_uid
< contents
[j
])
520 remve(contents
[j
++]);
523 if (cwret(cw
) == STOP
) {
524 fputs("Fatal: Cannot change back to current directory.\n",
532 longlt(const void *a
, const void *b
)
534 return *(const long*)a
- *(const long*)b
;
538 remve(unsigned long n
)
542 snprintf(buf
, sizeof buf
, "%lu", n
);
547 delcache(struct mailbox
*mp
, struct message
*m
)
551 fn
= encuid(mp
, m
->m_uid
);
552 if (fn
&& unlink(fn
) == 0)
553 m
->m_flag
|= MUNLINKED
;
557 cache_setptr(int transparent
)
562 unsigned long *contents
;
565 struct message
*omessage
= NULL
;
570 omsgCount
= msgCount
;
572 free(mb
.mb_cache_directory
);
573 mb
.mb_cache_directory
= NULL
;
574 if ((name
= encname(&mb
, "", 1, NULL
)) == NULL
)
576 mb
.mb_cache_directory
= sstrdup(name
);
577 if (cwget(&cw
) == STOP
)
581 contents
= builds(&contentelem
);
582 msgCount
= contentelem
;
583 message
= scalloc(msgCount
+ 1, sizeof *message
);
584 if (cwret(&cw
) == STOP
) {
585 fputs("Fatal: Cannot change back to current directory.\n",
590 for (i
= 0; i
< msgCount
; i
++) {
591 message
[i
].m_uid
= contents
[i
];
592 getcache1(&mb
, &message
[i
], NEED_UNSPEC
, 3);
596 mb
.mb_type
= MB_CACHE
;
597 mb
.mb_perm
= (options
& OPT_R_FLAG
) ? 0 : MB_DELE
;
599 transflags(omessage
, omsgCount
, 1);
607 cache_list(struct mailbox
*mp
, const char *base
, int strip
, FILE *fp
)
609 char *name
, *cachedir
, *eaccount
;
612 const char *cp
, *bp
, *sp
;
615 if ((cachedir
= value("imap-cache")) == NULL
||
616 (cachedir
= file_expand(cachedir
)) == NULL
)
618 eaccount
= urlxenc(mp
->mb_imap_account
);
619 name
= salloc(namesz
= strlen(cachedir
) + strlen(eaccount
) + 2);
620 snprintf(name
, namesz
, "%s/%s", cachedir
, eaccount
);
621 if ((dirp
= opendir(name
)) == NULL
)
623 while ((dp
= readdir(dirp
)) != NULL
) {
624 if (dp
->d_name
[0] == '.')
626 cp
= sp
= urlxdec(dp
->d_name
);
627 for (bp
= base
; *bp
&& *bp
== *sp
; bp
++)
631 cp
= strip
? sp
: cp
;
632 fprintf(fp
, "%s\n", *cp
? cp
: "INBOX");
639 cache_remove(const char *name
)
645 int pathsize
, pathend
, n
;
648 if ((dir
= encname(&mb
, "", 0, imap_fileof(name
))) == NULL
)
650 pathend
= strlen(dir
);
651 path
= smalloc(pathsize
= pathend
+ 30);
652 memcpy(path
, dir
, pathend
);
653 path
[pathend
++] = '/';
654 path
[pathend
] = '\0';
655 if ((dirp
= opendir(path
)) == NULL
) {
659 while ((dp
= readdir(dirp
)) != NULL
) {
660 if (dp
->d_name
[0] == '.' &&
661 (dp
->d_name
[1] == '\0' ||
662 (dp
->d_name
[1] == '.' &&
663 dp
->d_name
[2] == '\0')))
665 n
= strlen(dp
->d_name
) + 1;
666 if (pathend
+ n
> pathsize
)
667 path
= srealloc(path
, pathsize
= pathend
+ n
+ 30);
668 memcpy(path
+ pathend
, dp
->d_name
, n
);
669 if (stat(path
, &st
) < 0 || (st
.st_mode
&S_IFMT
) != S_IFREG
)
671 if (unlink(path
) < 0) {
679 path
[pathend
] = '\0';
680 rmdir(path
); /* no error on failure, might contain submailboxes */
686 cache_rename(const char *old
, const char *new)
688 char *olddir
, *newdir
;
690 if ((olddir
= encname(&mb
, "", 0, imap_fileof(old
))) == NULL
||
691 (newdir
= encname(&mb
, "",0, imap_fileof(new))) == NULL
)
693 if (rename(olddir
, newdir
) < 0) {
701 cached_uidvalidity(struct mailbox
*mp
)
707 if ((uvname
= encname(mp
, "UIDVALIDITY", 1, NULL
)) == NULL
)
709 if ((uvfp
= Fopen(uvname
, "r")) == NULL
||
710 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
711 fscanf(uvfp
, "%lu", &uv
) != 1)
719 cache_queue1(struct mailbox
*mp
, char const *mode
, char **xname
)
724 if ((name
= encname(mp
, "QUEUE", 0, NULL
)) == NULL
)
726 if ((fp
= Fopen(name
, mode
)) != NULL
)
727 fcntl_lock(fileno(fp
), F_WRLCK
);
734 cache_queue(struct mailbox
*mp
)
738 fp
= cache_queue1(mp
, "a", NULL
);
740 fputs("Cannot queue IMAP command. Retry when online.\n",
746 cache_dequeue(struct mailbox
*mp
)
749 char *cachedir
, *eaccount
, *buf
, *oldbox
;
753 if ((cachedir
= value("imap-cache")) == NULL
||
754 (cachedir
= file_expand(cachedir
)) == NULL
)
756 eaccount
= urlxenc(mp
->mb_imap_account
);
757 buf
= salloc(bufsz
= strlen(cachedir
) + strlen(eaccount
) + 2);
758 snprintf(buf
, bufsz
, "%s/%s", cachedir
, eaccount
);
759 if ((dirp
= opendir(buf
)) == NULL
)
761 oldbox
= mp
->mb_imap_mailbox
;
762 while ((dp
= readdir(dirp
)) != NULL
) {
763 if (dp
->d_name
[0] == '.')
765 mp
->mb_imap_mailbox
= urlxdec(dp
->d_name
);
769 mp
->mb_imap_mailbox
= oldbox
;
774 dequeue1(struct mailbox
*mp
)
776 FILE *fp
= NULL
, *uvfp
= NULL
;
777 char *qname
, *uvname
;
782 fp
= cache_queue1(mp
, "r+", &qname
);
783 if (fp
!= NULL
&& fsize(fp
) > 0) {
784 if (imap_select(mp
, &is_size
, &is_count
,
785 mp
->mb_imap_mailbox
) != OKAY
) {
786 fprintf(stderr
, "Cannot select \"%s\" for dequeuing.\n",
787 mp
->mb_imap_mailbox
);
790 if ((uvname
= encname(mp
, "UIDVALIDITY", 0, NULL
)) == NULL
||
791 (uvfp
= Fopen(uvname
, "r")) == NULL
||
792 (fcntl_lock(fileno(uvfp
), F_RDLCK
), 0) ||
793 fscanf(uvfp
, "%lu", &uv
) != 1 ||
794 uv
!= mp
->mb_uidvalidity
) {
796 "Unique identifiers for \"%s\" are out of date. "
797 "Cannot commit IMAP commands.\n",
798 mp
->mb_imap_mailbox
);
799 save
: fputs("Saving IMAP commands to dead.letter\n", stderr
);
800 savedeadletter(fp
, 0);
801 ftruncate(fileno(fp
), 0);
808 printf("Committing IMAP commands for \"%s\"\n",
809 mp
->mb_imap_mailbox
);
810 imap_dequeue(mp
, fp
);
818 #endif /* HAVE_IMAP */