Add *message-id-disable*
[s-mailx.git] / imap_cache.c
blob0e9688274a969373815db98d34f2c81fdb468c1a
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ A cache for IMAP.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2013 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 2004
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
13 * are met:
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
37 * SUCH DAMAGE.
40 #include "config.h"
42 #ifndef HAVE_IMAP
43 typedef int avoid_empty_file_compiler_warning;
44 #else
45 #include "rcv.h"
47 #include <sys/stat.h>
48 #include <errno.h>
49 #include <dirent.h>
50 #include <fcntl.h>
51 #include <termios.h>
52 #include <time.h>
53 #include <unistd.h>
55 #include "extern.h"
57 static char *encname(struct mailbox *mp, const char *name, int same,
58 const char *box);
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";
70 #define INITSKIP 128L
71 #define USEBITS(f) \
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";
99 static char *
100 encname(struct mailbox *mp, const char *name, int same, const char *box)
102 char *cachedir, *eaccount, *ename, *res;
103 char const *emailbox;
104 int resz;
106 ename = urlxenc(name);
107 if (mp->mb_cache_directory && same && box == NULL) {
108 res = salloc(resz = strlen(mp->mb_cache_directory) +
109 strlen(ename) + 2);
110 snprintf(res, resz, "%s%s%s", mp->mb_cache_directory,
111 *ename ? "/" : "", ename);
112 } else {
113 if ((cachedir = value("imap-cache")) == NULL ||
114 (cachedir = file_expand(cachedir)) == NULL)
115 return NULL;
116 eaccount = urlxenc(mp->mb_imap_account);
117 if (box)
118 emailbox = urlxenc(box);
119 else if (asccasecmp(mp->mb_imap_mailbox, "INBOX"))
120 emailbox = urlxenc(mp->mb_imap_mailbox);
121 else
122 emailbox = "INBOX";
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);
129 return res;
132 static char *
133 encuid(struct mailbox *mp, unsigned long uid)
135 char buf[30];
137 snprintf(buf, sizeof buf, "%lu", uid);
138 return encname(mp, buf, 1, NULL);
141 enum okay
142 getcache1(struct mailbox *mp, struct message *m, enum needspec need,
143 int setflags)
145 FILE *fp;
146 long n = 0, size = 0, xsize, xtime, xlines = -1, lines = 0;
147 int lastc = EOF, i, xflag, inheader = 1;
148 char b, iob[32768];
149 off_t offset;
150 void *zp;
152 if (setflags == 0 && ((mp->mb_type != MB_IMAP &&
153 mp->mb_type != MB_CACHE) ||
154 m->m_uid == 0))
155 return STOP;
156 if ((fp = Fopen(encuid(mp, m->m_uid), "r")) == NULL)
157 return STOP;
158 (void)fcntl_lock(fileno(fp), F_RDLCK);
159 if (fscanf(fp, infofmt, &b, (unsigned long*)&xsize, &xflag,
160 (unsigned long*)&xtime, &xlines) < 4)
161 goto fail;
162 if (need != NEED_UNSPEC) {
163 switch (b) {
164 case 'H':
165 if (need == NEED_HEADER)
166 goto success;
167 goto fail;
168 case 'B':
169 if (need == NEED_HEADER || need == NEED_BODY)
170 goto success;
171 goto fail;
172 default:
173 goto fail;
176 success:
177 if (b == 'N')
178 goto flags;
179 if (fseek(fp, INITSKIP, SEEK_SET) < 0)
180 goto fail;
181 zp = zalloc(fp);
182 if (fseek(mp->mb_otf, 0L, SEEK_END) < 0) {
183 (void)zfree(zp);
184 goto fail;
186 offset = ftell(mp->mb_otf);
187 while (inheader && (n = zread(zp, iob, sizeof iob)) > 0) {
188 size += n;
189 for (i = 0; i < n; i++) {
190 if (iob[i] == '\n') {
191 lines++;
192 if (lastc == '\n')
193 inheader = 0;
195 lastc = iob[i]&0377;
197 fwrite(iob, 1, n, mp->mb_otf);
199 if (n > 0 && need == NEED_BODY) {
200 while ((n = zread(zp, iob, sizeof iob)) > 0) {
201 size += n;
202 for (i = 0; i < n; i++)
203 if (iob[i] == '\n')
204 lines++;
205 fwrite(iob, 1, n, mp->mb_otf);
208 fflush(mp->mb_otf);
209 if (zfree(zp) < 0 || n < 0 || ferror(fp) || ferror(mp->mb_otf))
210 goto fail;
211 m->m_size = size;
212 m->m_lines = lines;
213 m->m_block = mailx_blockof(offset);
214 m->m_offset = mailx_offsetof(offset);
215 flags: if (setflags) {
216 m->m_xsize = xsize;
217 m->m_time = xtime;
218 if (setflags & 2) {
219 m->m_flag = xflag | MNOFROM;
220 if (b != 'B')
221 m->m_flag |= MHIDDEN;
224 if (xlines > 0 && m->m_xlines <= 0)
225 m->m_xlines = xlines;
226 switch (b) {
227 case 'B':
228 m->m_xsize = xsize;
229 if (xflag == MREAD && xlines > 0)
230 m->m_flag |= MFULLYCACHED;
231 if (need == NEED_BODY) {
232 m->m_have |= HAVE_HEADER|HAVE_BODY;
233 if (m->m_lines > 0)
234 m->m_xlines = m->m_lines;
235 break;
237 /*FALLTHRU*/
238 case 'H':
239 m->m_have |= HAVE_HEADER;
240 break;
241 case 'N':
242 break;
244 Fclose(fp);
245 return OKAY;
246 fail:
247 Fclose(fp);
248 return STOP;
251 enum okay
252 getcache(struct mailbox *mp, struct message *m, enum needspec need)
254 return getcache1(mp, m, need, 0);
257 void
258 putcache(struct mailbox *mp, struct message *m)
260 FILE *ibuf, *obuf;
261 char *name, ob;
262 int c, oflag;
263 long n, cnt, oldoffset, osize, otime, olines = -1;
264 char iob[32768];
265 void *zp;
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)
270 return;
271 if (m->m_have & HAVE_BODY)
272 c = 'B';
273 else if (m->m_have & HAVE_HEADER)
274 c = 'H';
275 else if (m->m_have == HAVE_NOTHING)
276 c = 'N';
277 else
278 return;
279 if ((oldoffset = ftell(mp->mb_itf)) < 0) /* XXX weird err hdling */
280 oldoffset = 0;
281 if ((obuf = Fopen(name = encuid(mp, m->m_uid), "r+")) == NULL) {
282 if ((obuf = Fopen(name, "w")) == NULL)
283 return;
284 (void)fcntl_lock(fileno(obuf), F_WRLCK); /* XXX err hdl */
285 } else {
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 ||
296 (m->m_xlines > 0 &&
297 olines != m->m_xlines)) {
298 fflush(obuf);
299 rewind(obuf);
300 fprintf(obuf, infofmt, ob,
301 (unsigned long)m->m_xsize,
302 USEBITS(m->m_flag),
303 (unsigned long)m->m_time,
304 m->m_xlines);
305 putc('\n', obuf);
307 Fclose(obuf);
308 return;
310 fflush(obuf);
311 rewind(obuf);
312 ftruncate(fileno(obuf), 0);
314 if ((ibuf = setinput(mp, m, NEED_UNSPEC)) == NULL) {
315 Fclose(obuf);
316 return;
318 if (c == 'N')
319 goto done;
320 fseek(obuf, INITSKIP, SEEK_SET);
321 zp = zalloc(obuf);
322 cnt = m->m_size;
323 while (cnt > 0) {
324 n = cnt > (long)sizeof iob ? (long)sizeof iob : cnt;
325 cnt -= n;
326 if ((size_t)n != fread(iob, 1, n, ibuf) ||
327 n != (long)zwrite(zp, iob, n)) {
328 unlink(name);
329 zfree(zp);
330 goto out;
333 if (zfree(zp) < 0) {
334 unlink(name);
335 goto out;
337 done: rewind(obuf);
338 fprintf(obuf, infofmt, c, (unsigned long)m->m_xsize,
339 USEBITS(m->m_flag),
340 (unsigned long)m->m_time,
341 m->m_xlines);
342 putc('\n', obuf);
343 if (ferror(obuf)) {
344 unlink(name);
345 goto out;
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;
351 unlink(name);
353 (void)fseek(mp->mb_itf, oldoffset, SEEK_SET);
356 void
357 initcache(struct mailbox *mp)
359 char *name, *uvname;
360 FILE *uvfp;
361 unsigned long uv;
362 struct cw cw;
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)
368 return;
369 mp->mb_cache_directory = sstrdup(name);
370 if ((uvname = encname(mp, "UIDVALIDITY", 1, NULL)) == NULL)
371 return;
372 if (cwget(&cw) == STOP)
373 return;
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)
379 goto out;
380 } else {
381 fflush(uvfp);
382 rewind(uvfp);
384 fcntl_lock(fileno(uvfp), F_WRLCK);
385 fprintf(uvfp, "%lu\n", mp->mb_uidvalidity);
386 if (ferror(uvfp) || Fclose(uvfp) != 0) {
387 unlink(uvname);
388 mp->mb_uidvalidity = 0;
390 out: cwrelse(&cw);
393 void
394 purgecache(struct mailbox *mp, struct message *m, long mc)
396 char *name;
397 struct cw cw;
399 if ((name = encname(mp, "", 1, NULL)) == NULL)
400 return;
401 if (cwget(&cw) == STOP)
402 return;
403 purge(mp, m, mc, &cw, name);
404 cwrelse(&cw);
407 static FILE *
408 clean(struct mailbox *mp, struct cw *cw)
410 char *cachedir, *eaccount, *buf;
411 char const *emailbox;
412 int bufsz;
413 DIR *dirp;
414 struct dirent *dp;
415 FILE *fp = NULL;
417 if ((cachedir = value("imap-cache")) == NULL ||
418 (cachedir = file_expand(cachedir)) == NULL)
419 return NULL;
420 eaccount = urlxenc(mp->mb_imap_account);
421 if (asccasecmp(mp->mb_imap_mailbox, "INBOX"))
422 emailbox = urlxenc(mp->mb_imap_mailbox);
423 else
424 emailbox = "INBOX";
425 buf = salloc(bufsz = strlen(cachedir) + strlen(eaccount) +
426 strlen(emailbox) + 40);
427 if (makedir(cachedir) != OKAY)
428 return NULL;
429 snprintf(buf, bufsz, "%s/README", cachedir);
430 if ((fp = Fopen(buf, "wx")) != NULL) {
431 fputs(README1, fp);
432 fputs(README2, fp);
433 fputs(README3, fp);
434 fputs(README4, fp);
435 fputs(README5, fp);
436 Fclose(fp);
438 snprintf(buf, bufsz, "%s/%s", cachedir, eaccount);
439 if (makedir(buf) != OKAY)
440 return NULL;
441 snprintf(buf, bufsz, "%s/%s/%s", cachedir, eaccount, emailbox);
442 if (makedir(buf) != OKAY)
443 return NULL;
444 if (chdir(buf) < 0)
445 return NULL;
446 if ((dirp = opendir(".")) == NULL)
447 goto out;
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')))
453 continue;
454 unlink(dp->d_name);
456 closedir(dirp);
457 fp = Fopen("UIDVALIDITY", "w");
458 out: if (cwret(cw) == STOP) {
459 fputs("Fatal: Cannot change back to current directory.\n",
460 stderr);
461 abort();
463 return fp;
466 static unsigned long *
467 builds(long *contentelem)
469 unsigned long n, *contents = NULL;
470 long contentalloc = 0;
471 char *x;
472 DIR *dirp;
473 struct dirent *dp;
475 *contentelem = 0;
476 if ((dirp = opendir(".")) == NULL)
477 return 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')))
483 continue;
484 n = strtoul(dp->d_name, &x, 10);
485 if (*x != '\0')
486 continue;
487 if (*contentelem >= contentalloc - 1)
488 contents = srealloc(contents,
489 (contentalloc += 200) * sizeof *contents);
490 contents[(*contentelem)++] = n;
492 closedir(dirp);
493 if (*contentelem > 0) {
494 contents[*contentelem] = 0;
495 qsort(contents, *contentelem, sizeof *contents, longlt);
497 return contents;
500 static void
501 purge(struct mailbox *mp, struct message *m, long mc, struct cw *cw,
502 const char *name)
504 unsigned long *contents;
505 long i, j, contentelem;
506 (void)mp;
508 if (chdir(name) < 0)
509 return;
510 contents = builds(&contentelem);
511 if (contents) {
512 i = j = 0;
513 while (j < contentelem) {
514 if (i < mc && m[i].m_uid == contents[j]) {
515 i++;
516 j++;
517 } else if (i < mc && m[i].m_uid < contents[j])
518 i++;
519 else
520 remve(contents[j++]);
523 if (cwret(cw) == STOP) {
524 fputs("Fatal: Cannot change back to current directory.\n",
525 stderr);
526 abort();
528 free(contents);
531 static int
532 longlt(const void *a, const void *b)
534 return *(const long*)a - *(const long*)b;
537 static void
538 remve(unsigned long n)
540 char buf[30];
542 snprintf(buf, sizeof buf, "%lu", n);
543 unlink(buf);
546 void
547 delcache(struct mailbox *mp, struct message *m)
549 char *fn;
551 fn = encuid(mp, m->m_uid);
552 if (fn && unlink(fn) == 0)
553 m->m_flag |= MUNLINKED;
556 enum okay
557 cache_setptr(int transparent)
559 int i;
560 struct cw cw;
561 char *name;
562 unsigned long *contents;
563 long contentelem;
564 enum okay ok = STOP;
565 struct message *omessage = NULL;
566 int omsgCount = 0;
568 if (transparent) {
569 omessage = message;
570 omsgCount = msgCount;
572 free(mb.mb_cache_directory);
573 mb.mb_cache_directory = NULL;
574 if ((name = encname(&mb, "", 1, NULL)) == NULL)
575 return STOP;
576 mb.mb_cache_directory = sstrdup(name);
577 if (cwget(&cw) == STOP)
578 return STOP;
579 if (chdir(name) < 0)
580 return 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",
586 stderr);
587 abort();
589 cwrelse(&cw);
590 for (i = 0; i < msgCount; i++) {
591 message[i].m_uid = contents[i];
592 getcache1(&mb, &message[i], NEED_UNSPEC, 3);
594 ok = OKAY;
595 if (ok == OKAY) {
596 mb.mb_type = MB_CACHE;
597 mb.mb_perm = (options & OPT_R_FLAG) ? 0 : MB_DELE;
598 if (transparent)
599 transflags(omessage, omsgCount, 1);
600 else
601 setdot(message);
603 return ok;
606 enum okay
607 cache_list(struct mailbox *mp, const char *base, int strip, FILE *fp)
609 char *name, *cachedir, *eaccount;
610 DIR *dirp;
611 struct dirent *dp;
612 const char *cp, *bp, *sp;
613 int namesz;
615 if ((cachedir = value("imap-cache")) == NULL ||
616 (cachedir = file_expand(cachedir)) == NULL)
617 return STOP;
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)
622 return STOP;
623 while ((dp = readdir(dirp)) != NULL) {
624 if (dp->d_name[0] == '.')
625 continue;
626 cp = sp = urlxdec(dp->d_name);
627 for (bp = base; *bp && *bp == *sp; bp++)
628 sp++;
629 if (*bp)
630 continue;
631 cp = strip ? sp : cp;
632 fprintf(fp, "%s\n", *cp ? cp : "INBOX");
634 closedir(dirp);
635 return OKAY;
638 enum okay
639 cache_remove(const char *name)
641 struct stat st;
642 DIR *dirp;
643 struct dirent *dp;
644 char *path;
645 int pathsize, pathend, n;
646 char *dir;
648 if ((dir = encname(&mb, "", 0, imap_fileof(name))) == NULL)
649 return OKAY;
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) {
656 free(path);
657 return OKAY;
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')))
664 continue;
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)
670 continue;
671 if (unlink(path) < 0) {
672 perror(path);
673 closedir(dirp);
674 free(path);
675 return STOP;
678 closedir(dirp);
679 path[pathend] = '\0';
680 rmdir(path); /* no error on failure, might contain submailboxes */
681 free(path);
682 return OKAY;
685 enum okay
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)
692 return OKAY;
693 if (rename(olddir, newdir) < 0) {
694 perror(olddir);
695 return STOP;
697 return OKAY;
700 unsigned long
701 cached_uidvalidity(struct mailbox *mp)
703 FILE *uvfp;
704 char *uvname;
705 unsigned long uv;
707 if ((uvname = encname(mp, "UIDVALIDITY", 1, NULL)) == NULL)
708 return 0;
709 if ((uvfp = Fopen(uvname, "r")) == NULL ||
710 (fcntl_lock(fileno(uvfp), F_RDLCK), 0) ||
711 fscanf(uvfp, "%lu", &uv) != 1)
712 uv = 0;
713 if (uvfp != NULL)
714 Fclose(uvfp);
715 return uv;
718 static FILE *
719 cache_queue1(struct mailbox *mp, char const *mode, char **xname)
721 char *name;
722 FILE *fp;
724 if ((name = encname(mp, "QUEUE", 0, NULL)) == NULL)
725 return NULL;
726 if ((fp = Fopen(name, mode)) != NULL)
727 fcntl_lock(fileno(fp), F_WRLCK);
728 if (xname)
729 *xname = name;
730 return fp;
733 FILE *
734 cache_queue(struct mailbox *mp)
736 FILE *fp;
738 fp = cache_queue1(mp, "a", NULL);
739 if (fp == NULL)
740 fputs("Cannot queue IMAP command. Retry when online.\n",
741 stderr);
742 return fp;
745 enum okay
746 cache_dequeue(struct mailbox *mp)
748 int bufsz;
749 char *cachedir, *eaccount, *buf, *oldbox;
750 DIR *dirp;
751 struct dirent *dp;
753 if ((cachedir = value("imap-cache")) == NULL ||
754 (cachedir = file_expand(cachedir)) == NULL)
755 return OKAY;
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)
760 return OKAY;
761 oldbox = mp->mb_imap_mailbox;
762 while ((dp = readdir(dirp)) != NULL) {
763 if (dp->d_name[0] == '.')
764 continue;
765 mp->mb_imap_mailbox = urlxdec(dp->d_name);
766 dequeue1(mp);
768 closedir(dirp);
769 mp->mb_imap_mailbox = oldbox;
770 return OKAY;
773 static enum okay
774 dequeue1(struct mailbox *mp)
776 FILE *fp = NULL, *uvfp = NULL;
777 char *qname, *uvname;
778 unsigned long uv;
779 off_t is_size;
780 int is_count;
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);
788 goto save;
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) {
795 fprintf(stderr,
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);
802 Fclose(fp);
803 if (uvfp)
804 Fclose(uvfp);
805 return STOP;
807 Fclose(uvfp);
808 printf("Committing IMAP commands for \"%s\"\n",
809 mp->mb_imap_mailbox);
810 imap_dequeue(mp, fp);
812 if (fp) {
813 Fclose(fp);
814 unlink(qname);
816 return OKAY;
818 #endif /* HAVE_IMAP */