1 /*
2  * Heirloom mailx - a mail user agent derived from Berkeley Mail.
3  *
4  * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5  */
6 /*
7  * Copyright (c) 2004
8  *	Gunnar Ritter.  All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *	This product includes software developed by Gunnar Ritter
21  *	and his contributors.
22  * 4. Neither the name of Gunnar Ritter nor the names of his contributors
23  *    may be used to endorse or promote products derived from this software
24  *    without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
27  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29  * ARE DISCLAIMED.  IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
30  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36  * SUCH DAMAGE.
37  */
38 
39 #ifndef lint
40 #ifdef	DOSCCS
41 static char sccsid[] = "@(#)cache.c	1.61 (gritter) 3/4/06";
42 #endif
43 #endif /* not lint */
44 
45 #include "config.h"
46 
47 #ifdef	HAVE_SOCKETS
48 
49 #include "rcv.h"
50 #include "extern.h"
51 #include <errno.h>
52 #include <sys/stat.h>
53 #include <unistd.h>
54 #include <time.h>
55 #include <dirent.h>
56 #include <fcntl.h>
57 
58 #include <termios.h>
59 
60 /*
61  * Mail -- a mail program
62  *
63  * A cache for IMAP.
64  */
65 
66 static char *encname(struct mailbox *mp, const char *name, int same,
67 		const char *box);
68 static char *encuid(struct mailbox *mp, unsigned long uid);
69 static FILE *clean(struct mailbox *mp, struct cw *cw);
70 static unsigned long *builds(long *contentelem);
71 static void purge(struct mailbox *mp, struct message *m, long mc,
72 		struct cw *cw, const char *name);
73 static int longlt(const void *a, const void *b);
74 static void remve(unsigned long n);
75 static FILE *cache_queue1(struct mailbox *mp, char *mode, char **xname);
76 static enum okay dequeue1(struct mailbox *mp);
77 
78 static const char	infofmt[] = "%c %lu %u %lu %lu";
79 #define	INITSKIP	128L
80 #define	USEBITS(f)	\
81 	((f) & (MSAVED|MDELETED|MREAD|MBOXED|MNEW|MFLAGGED|MANSWERED|MDRAFTED))
82 
83 static const char	README1[] = "\
84 This is a cache directory maintained by mailx(1). You should not change any\n\
85 files within. Nevertheless, the structure is as follows: Each subdirectory\n\
86 of the current directory represents an IMAP account, and each subdirectory\n\
87 below that represents a mailbox. Each mailbox directory contains a file\n\
88 named UIDVALIDITY which describes the validity in relation to the version\n\
89 on the server. Other files have names corresponding to their IMAP UID.\n";
90 static const char	README2[] = "\n\
91 The first 128 bytes of these files are used to store message attributes; the\n\
92 following data is equivalent to compress(1) output. So if you have to save a\n\
93 message by hand because of an emergency, throw away the first 128 bytes and\n\
94 decompress the rest, as e.g. 'dd if=MESSAGEFILE skip=1 bs=128 | zcat' does.\n";
95 static const char	README3[] = "\n\
96 Files named QUEUE contain data that will be sent do the IMAP server next\n\
97 time a connection is made in online mode.\n";
98 static const char	README4[] = "\n\
99 You can safely delete any file or directory here, unless it contains a QUEUE\n\
100 file that is not empty; mailx(1) will download the data again and will also\n\
101 write new cache entries if configured in this way. If you do not wish to use\n\
102 the cache anymore, delete the entire directory and unset the 'imap-cache'\n\
103 variable in mailx(1).\n";
104 static const char	README5[] = "\n\
105 For more information about mailx(1), visit\n\
106 <http://heirloom.sourceforge.net/mailx.html>.\n";
107 
108 static char *
encname(struct mailbox * mp,const char * name,int same,const char * box)109 encname(struct mailbox *mp, const char *name, int same, const char *box)
110 {
111 	char	*cachedir, *eaccount, *emailbox, *ename, *res;
112 	int	resz;
113 
114 	ename = strenc(name);
115 	if (mp->mb_cache_directory && same && box == NULL) {
116 		res = salloc(resz = strlen(mp->mb_cache_directory) +
117 				strlen(ename) + 2);
118 		snprintf(res, resz, "%s%s%s", mp->mb_cache_directory,
119 				*ename ? "/" : "", ename);
120 	} else {
121 		if ((cachedir = value("imap-cache")) == NULL)
122 			return NULL;
123 		cachedir = expand(cachedir);
124 		eaccount = strenc(mp->mb_imap_account);
125 		if (box)
126 			emailbox = strenc(box);
127 		else if (asccasecmp(mp->mb_imap_mailbox, "INBOX"))
128 			emailbox = strenc(mp->mb_imap_mailbox);
129 		else
130 			emailbox = "INBOX";
131 		res = salloc(resz = strlen(cachedir) + strlen(eaccount) +
132 				strlen(emailbox) + strlen(ename) + 4);
133 		snprintf(res, resz, "%s/%s/%s%s%s",
134 				cachedir, eaccount, emailbox,
135 				*ename ? "/" : "", ename);
136 	}
137 	return res;
138 }
139 
140 static char *
encuid(struct mailbox * mp,unsigned long uid)141 encuid(struct mailbox *mp, unsigned long uid)
142 {
143 	char	buf[30];
144 
145 	snprintf(buf, sizeof buf, "%lu", uid);
146 	return encname(mp, buf, 1, NULL);
147 }
148 
149 enum okay
getcache1(struct mailbox * mp,struct message * m,enum needspec need,int setflags)150 getcache1(struct mailbox *mp, struct message *m, enum needspec need,
151 		int setflags)
152 {
153 	FILE	*fp;
154 	long	n = 0, size = 0, xsize, xtime, xlines = -1, lines = 0;
155 	int	lastc = EOF, i, xflag, inheader = 1;
156 	char	b, iob[32768];
157 	off_t	offset;
158 	void	*zp;
159 
160 	if (setflags == 0 && ((mp->mb_type != MB_IMAP &&
161 					mp->mb_type != MB_CACHE) ||
162 			m->m_uid == 0))
163 		return STOP;
164 	if ((fp = Fopen(encuid(mp, m->m_uid), "r")) == NULL)
165 		return STOP;
166 	fcntl_lock(fileno(fp), F_RDLCK);
167 	if (fscanf(fp, infofmt, &b, &xsize, &xflag, &xtime, &xlines) < 4)
168 		goto fail;
169 	if (need != NEED_UNSPEC) {
170 		switch (b) {
171 		case 'H':
172 			if (need == NEED_HEADER)
173 				goto success;
174 			goto fail;
175 		case 'B':
176 			if (need == NEED_HEADER || need == NEED_BODY)
177 				goto success;
178 			goto fail;
179 		default:
180 			goto fail;
181 		}
182 	}
183 success:
184 	if (b == 'N')
185 		goto flags;
186 	fseek(fp, INITSKIP, SEEK_SET);
187 	zp = zalloc(fp);
188 	fseek(mp->mb_otf, 0L, SEEK_END);
189 	offset = ftell(mp->mb_otf);
190 	while (inheader && (n = zread(zp, iob, sizeof iob)) > 0) {
191 		size += n;
192 		for (i = 0; i < n; i++) {
193 			if (iob[i] == '\n') {
194 				lines++;
195 				if (lastc == '\n')
196 					inheader = 0;
197 			}
198 			lastc = iob[i]&0377;
199 		}
200 		fwrite(iob, 1, n, mp->mb_otf);
201 	}
202 	if (n > 0 && need == NEED_BODY) {
203 		while ((n = zread(zp, iob, sizeof iob)) > 0) {
204 			size += n;
205 			for (i = 0; i < n; i++)
206 				if (iob[i] == '\n')
207 					lines++;
208 			fwrite(iob, 1, n, mp->mb_otf);
209 		}
210 	}
211 	fflush(mp->mb_otf);
212 	if (zfree(zp) < 0 || n < 0 || ferror(fp) || ferror(mp->mb_otf))
213 		goto fail;
214 	m->m_size = size;
215 	m->m_lines = lines;
216 	m->m_block = mailx_blockof(offset);
217 	m->m_offset = mailx_offsetof(offset);
218 flags:	if (setflags) {
219 		m->m_xsize = xsize;
220 		m->m_time = xtime;
221 		if (setflags & 2) {
222 			m->m_flag = xflag | MNOFROM;
223 			if (b != 'B')
224 				m->m_flag |= MHIDDEN;
225 		}
226 	}
227 	if (xlines > 0 && m->m_xlines <= 0)
228 		m->m_xlines = xlines;
229 	switch (b) {
230 	case 'B':
231 		m->m_xsize = xsize;
232 		if (xflag == MREAD && xlines > 0)
233 			m->m_flag |= MFULLYCACHED;
234 		if (need == NEED_BODY) {
235 			m->m_have |= HAVE_HEADER|HAVE_BODY;
236 			if (m->m_lines > 0)
237 				m->m_xlines = m->m_lines;
238 			break;
239 		}
240 		/*FALLTHRU*/
241 	case 'H':
242 		m->m_have |= HAVE_HEADER;
243 		break;
244 	case 'N':
245 		break;
246 	}
247 	Fclose(fp);
248 	return OKAY;
249 fail:
250 	Fclose(fp);
251 	return STOP;
252 }
253 
254 enum okay
getcache(struct mailbox * mp,struct message * m,enum needspec need)255 getcache(struct mailbox *mp, struct message *m, enum needspec need)
256 {
257 	return getcache1(mp, m, need, 0);
258 }
259 
260 void
putcache(struct mailbox * mp,struct message * m)261 putcache(struct mailbox *mp, struct message *m)
262 {
263 	FILE	*ibuf, *obuf;
264 	char	*name, ob;
265 	int	c, oflag;
266 	long	n, count, oldoffset, osize, otime, olines = -1;
267 	char	iob[32768];
268 	void	*zp;
269 
270 	if ((mp->mb_type != MB_IMAP && mp->mb_type != MB_CACHE) ||
271 			m->m_uid == 0 || m->m_time == 0 ||
272 			(m->m_flag & (MTOUCH|MFULLYCACHED)) == MFULLYCACHED)
273 		return;
274 	if (m->m_have & HAVE_BODY)
275 		c = 'B';
276 	else if (m->m_have & HAVE_HEADER)
277 		c = 'H';
278 	else if (m->m_have == HAVE_NOTHING)
279 		c = 'N';
280 	else
281 		return;
282 	oldoffset = ftell(mp->mb_itf);
283 	if ((obuf = Fopen(name = encuid(mp, m->m_uid), "r+")) == NULL) {
284 		if ((obuf = Fopen(name, "w")) == NULL)
285 			return;
286 		fcntl_lock(fileno(obuf), F_WRLCK);
287 	} else {
288 		fcntl_lock(fileno(obuf), F_WRLCK);
289 		if (fscanf(obuf, infofmt, &ob, &osize, &oflag, &otime,
290 					&olines) >= 4 && ob != '\0' &&
291 				(ob == 'B' || (ob == 'H' && c != 'B'))) {
292 			if (m->m_xlines <= 0 && olines > 0)
293 				m->m_xlines = olines;
294 			if ((c != 'N' && osize != m->m_xsize) ||
295 					oflag != USEBITS(m->m_flag) ||
296 					otime != m->m_time ||
297 					(m->m_xlines > 0 &&
298 					 olines != m->m_xlines)) {
299 				fflush(obuf);
300 				rewind(obuf);
301 				fprintf(obuf, infofmt, ob,
302 					(long)m->m_xsize,
303 					USEBITS(m->m_flag),
304 					(long)m->m_time,
305 					m->m_xlines);
306 				putc('\n', obuf);
307 			}
308 			Fclose(obuf);
309 			return;
310 		}
311 		fflush(obuf);
312 		rewind(obuf);
313 		ftruncate(fileno(obuf), 0);
314 	}
315 	if ((ibuf = setinput(mp, m, NEED_UNSPEC)) == NULL) {
316 		Fclose(obuf);
317 		return;
318 	}
319 	if (c == 'N')
320 		goto done;
321 	fseek(obuf, INITSKIP, SEEK_SET);
322 	zp = zalloc(obuf);
323 	count = m->m_size;
324 	while (count > 0) {
325 		n = count > sizeof iob ? sizeof iob : count;
326 		count -= n;
327 		if (fread(iob, 1, n, ibuf) != n || zwrite(zp, iob, n) != n) {
328 			unlink(name);
329 			zfree(zp);
330 			goto out;
331 		}
332 	}
333 	if (zfree(zp) < 0) {
334 		unlink(name);
335 		goto out;
336 	}
337 done:	rewind(obuf);
338 	fprintf(obuf, infofmt, c, (long)m->m_xsize,
339 			USEBITS(m->m_flag),
340 			(long)m->m_time,
341 			m->m_xlines);
342 	putc('\n', obuf);
343 	if (ferror(obuf)) {
344 		unlink(name);
345 		goto out;
346 	}
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);
352 	}
353 	fseek(mp->mb_itf, oldoffset, SEEK_SET);
354 }
355 
356 void
initcache(struct mailbox * mp)357 initcache(struct mailbox *mp)
358 {
359 	char	*name, *uvname;
360 	FILE	*uvfp;
361 	unsigned long	uv;
362 	struct cw	cw;
363 
364 	free(mp->mb_cache_directory);
365 	mp->mb_cache_directory = NULL;
366 	if ((name = encname(mp, "", 1, NULL)) == NULL)
367 		return;
368 	mp->mb_cache_directory = sstrdup(name);
369 	if ((uvname = encname(mp, "UIDVALIDITY", 1, NULL)) == NULL)
370 		return;
371 	if (cwget(&cw) == STOP)
372 		return;
373 	if ((uvfp = Fopen(uvname, "r+")) == NULL ||
374 			(fcntl_lock(fileno(uvfp), F_RDLCK), 0) ||
375 			fscanf(uvfp, "%lu", &uv) != 1 ||
376 			uv != mp->mb_uidvalidity) {
377 		if ((uvfp = clean(mp, &cw)) == NULL)
378 			goto out;
379 	} else {
380 		fflush(uvfp);
381 		rewind(uvfp);
382 	}
383 	fcntl_lock(fileno(uvfp), F_WRLCK);
384 	fprintf(uvfp, "%lu\n", mp->mb_uidvalidity);
385 	if (ferror(uvfp) || Fclose(uvfp) != 0) {
386 		unlink(uvname);
387 		mp->mb_uidvalidity = 0;
388 	}
389 out:	cwrelse(&cw);
390 }
391 
392 void
purgecache(struct mailbox * mp,struct message * m,long mc)393 purgecache(struct mailbox *mp, struct message *m, long mc)
394 {
395 	char	*name;
396 	struct cw	cw;
397 
398 	if ((name = encname(mp, "", 1, NULL)) == NULL)
399 		return;
400 	if (cwget(&cw) == STOP)
401 		return;
402 	purge(mp, m, mc, &cw, name);
403 	cwrelse(&cw);
404 }
405 
406 static FILE *
clean(struct mailbox * mp,struct cw * cw)407 clean(struct mailbox *mp, struct cw *cw)
408 {
409 	char	*cachedir, *eaccount, *emailbox, *buf;
410 	int	bufsz;
411 	DIR	*dirfd;
412 	struct dirent	*dp;
413 	FILE	*fp = NULL;
414 
415 	if ((cachedir = value("imap-cache")) == NULL)
416 		return NULL;
417 	cachedir = expand(cachedir);
418 	eaccount = strenc(mp->mb_imap_account);
419 	if (asccasecmp(mp->mb_imap_mailbox, "INBOX"))
420 		emailbox = strenc(mp->mb_imap_mailbox);
421 	else
422 		emailbox = "INBOX";
423 	buf = salloc(bufsz = strlen(cachedir) + strlen(eaccount) +
424 			strlen(emailbox) + 40);
425 	if (makedir(cachedir) != OKAY)
426 		return NULL;
427 	snprintf(buf, bufsz, "%s/README", cachedir);
428 	if ((fp = Fopen(buf, "wx")) != NULL) {
429 		fputs(README1, fp);
430 		fputs(README2, fp);
431 		fputs(README3, fp);
432 		fputs(README4, fp);
433 		fputs(README5, fp);
434 		Fclose(fp);
435 	}
436 	snprintf(buf, bufsz, "%s/%s", cachedir, eaccount);
437 	if (makedir(buf) != OKAY)
438 		return NULL;
439 	snprintf(buf, bufsz, "%s/%s/%s", cachedir, eaccount, emailbox);
440 	if (makedir(buf) != OKAY)
441 		return NULL;
442 	if (chdir(buf) < 0)
443 		return NULL;
444 	if ((dirfd = opendir(".")) == NULL)
445 		goto out;
446 	while ((dp = readdir(dirfd)) != NULL) {
447 		if (dp->d_name[0] == '.' &&
448 				(dp->d_name[1] == '\0' ||
449 				 (dp->d_name[1] == '.' &&
450 				  dp->d_name[2] == '\0')))
451 			continue;
452 		unlink(dp->d_name);
453 	}
454 	closedir(dirfd);
455 	fp = Fopen("UIDVALIDITY", "w");
456 out:	if (cwret(cw) == STOP) {
457 		fputs("Fatal: Cannot change back to current directory.\n",
458 				stderr);
459 		abort();
460 	}
461 	return fp;
462 }
463 
464 static unsigned long *
builds(long * contentelem)465 builds(long *contentelem)
466 {
467 	unsigned long	n, *contents = NULL;
468 	long	contentalloc = 0;
469 	char	*x;
470 	DIR	*dirfd;
471 	struct dirent	*dp;
472 
473 	*contentelem = 0;
474 	if ((dirfd = opendir(".")) == NULL)
475 		return NULL;
476 	while ((dp = readdir(dirfd)) != NULL) {
477 		if (dp->d_name[0] == '.' &&
478 				(dp->d_name[1] == '\0' ||
479 				 (dp->d_name[1] == '.' &&
480 				  dp->d_name[2] == '\0')))
481 			continue;
482 		n = strtoul(dp->d_name, &x, 10);
483 		if (*x != '\0')
484 			continue;
485 		if (*contentelem >= contentalloc - 1)
486 			contents = srealloc(contents,
487 				(contentalloc += 200) * sizeof *contents);
488 		contents[(*contentelem)++] = n;
489 	}
490 	closedir(dirfd);
491 	if (*contentelem > 0) {
492 		contents[*contentelem] = 0;
493 		qsort(contents, *contentelem, sizeof *contents, longlt);
494 	}
495 	return contents;
496 }
497 
498 static void
purge(struct mailbox * mp,struct message * m,long mc,struct cw * cw,const char * name)499 purge(struct mailbox *mp, struct message *m, long mc, struct cw *cw,
500 		const char *name)
501 {
502 	unsigned long	*contents;
503 	long	i, j, contentelem;
504 
505 	if (chdir(name) < 0)
506 		return;
507 	contents = builds(&contentelem);
508 	if (contents) {
509 		i = j = 0;
510 		while (j < contentelem) {
511 			if (i < mc && m[i].m_uid == contents[j]) {
512 				i++;
513 				j++;
514 			} else if (i < mc && m[i].m_uid < contents[j])
515 				i++;
516 			else
517 				remve(contents[j++]);
518 		}
519 	}
520 	if (cwret(cw) == STOP) {
521 		fputs("Fatal: Cannot change back to current directory.\n",
522 				stderr);
523 		abort();
524 	}
525 	free(contents);
526 }
527 
528 static int
longlt(const void * a,const void * b)529 longlt(const void *a, const void *b)
530 {
531 	return *(long *)a - *(long *)b;
532 }
533 
534 static void
remve(unsigned long n)535 remve(unsigned long n)
536 {
537 	char	buf[30];
538 
539 	snprintf(buf, sizeof buf, "%lu", n);
540 	unlink(buf);
541 }
542 
543 void
delcache(struct mailbox * mp,struct message * m)544 delcache(struct mailbox *mp, struct message *m)
545 {
546 	char	*fn;
547 
548 	fn = encuid(mp, m->m_uid);
549 	if (fn && unlink(fn) == 0)
550 		m->m_flag |= MUNLINKED;
551 }
552 
553 enum okay
cache_setptr(int transparent)554 cache_setptr(int transparent)
555 {
556 	int	i;
557 	struct cw	cw;
558 	char	*name;
559 	unsigned long	*contents;
560 	long	contentelem;
561 	enum okay	ok = STOP;
562 	struct message	*omessage = NULL;
563 	int	omsgCount = 0;
564 
565 	if (transparent) {
566 		omessage = message;
567 		omsgCount = msgCount;
568 	}
569 	free(mb.mb_cache_directory);
570 	mb.mb_cache_directory = NULL;
571 	if ((name = encname(&mb, "", 1, NULL)) == NULL)
572 		return STOP;
573 	mb.mb_cache_directory = sstrdup(name);
574 	if (cwget(&cw) == STOP)
575 		return STOP;
576 	if (chdir(name) < 0)
577 		return STOP;
578 	contents = builds(&contentelem);
579 	msgCount = contentelem;
580 	message = scalloc(msgCount + 1, sizeof *message);
581 	if (cwret(&cw) == STOP) {
582 		fputs("Fatal: Cannot change back to current directory.\n",
583 				stderr);
584 		abort();
585 	}
586 	cwrelse(&cw);
587 	for (i = 0; i < msgCount; i++) {
588 		message[i].m_uid = contents[i];
589 		getcache1(&mb, &message[i], NEED_UNSPEC, 3);
590 	}
591 	ok = OKAY;
592 	if (ok == OKAY) {
593 		mb.mb_type = MB_CACHE;
594 		mb.mb_perm = Rflag ? 0 : MB_DELE;
595 		if (transparent)
596 			transflags(omessage, omsgCount, 1);
597 		else
598 			setdot(message);
599 	}
600 	return ok;
601 }
602 
603 enum okay
cache_list(struct mailbox * mp,const char * base,int strip,FILE * fp)604 cache_list(struct mailbox *mp, const char *base, int strip, FILE *fp)
605 {
606 	char	*name, *cachedir, *eaccount;
607 	DIR	*dirfd;
608 	struct dirent	*dp;
609 	const char	*cp, *bp, *sp;
610 	int	namesz;
611 
612 	if ((cachedir = value("imap-cache")) == NULL)
613 		return STOP;
614 	cachedir = expand(cachedir);
615 	eaccount = strenc(mp->mb_imap_account);
616 	name = salloc(namesz = strlen(cachedir) + strlen(eaccount) + 2);
617 	snprintf(name, namesz, "%s/%s", cachedir, eaccount);
618 	if ((dirfd = opendir(name)) == NULL)
619 		return STOP;
620 	while ((dp = readdir(dirfd)) != NULL) {
621 		if (dp->d_name[0] == '.')
622 			continue;
623 		cp = sp = strdec(dp->d_name);
624 		for (bp = base; *bp && *bp == *sp; bp++)
625 			sp++;
626 		if (*bp)
627 			continue;
628 		cp = strip ? sp : cp;
629 		fprintf(fp, "%s\n", *cp ? cp : "INBOX");
630 	}
631 	closedir(dirfd);
632 	return OKAY;
633 }
634 
635 enum okay
cache_remove(const char * name)636 cache_remove(const char *name)
637 {
638 	struct stat	st;
639 	DIR	*dirfd;
640 	struct dirent	*dp;
641 	char	*path;
642 	int	pathsize, pathend, n;
643 	char	*dir;
644 
645 	if ((dir = encname(&mb, "", 0, protfile(name))) == NULL)
646 		return OKAY;
647 	pathend = strlen(dir);
648 	path = smalloc(pathsize = pathend + 30);
649 	strcpy(path, dir);
650 	path[pathend++] = '/';
651 	path[pathend] = '\0';
652 	if ((dirfd = opendir(path)) == NULL) {
653 		free(path);
654 		return OKAY;
655 	}
656 	while ((dp = readdir(dirfd)) != NULL) {
657 		if (dp->d_name[0] == '.' &&
658 				(dp->d_name[1] == '\0' ||
659 				 (dp->d_name[1] == '.' &&
660 				  dp->d_name[2] == '\0')))
661 			continue;
662 		n = strlen(dp->d_name);
663 		if (pathend + n + 1 > pathsize)
664 			path = srealloc(path, pathsize = pathend + n + 30);
665 		strcpy(&path[pathend], dp->d_name);
666 		if (stat(path, &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)
667 			continue;
668 		if (unlink(path) < 0) {
669 			perror(path);
670 			closedir(dirfd);
671 			free(path);
672 			return STOP;
673 		}
674 	}
675 	closedir(dirfd);
676 	path[pathend] = '\0';
677 	rmdir(path);	/* no error on failure, might contain submailboxes */
678 	free(path);
679 	return OKAY;
680 }
681 
682 enum okay
cache_rename(const char * old,const char * new)683 cache_rename(const char *old, const char *new)
684 {
685 	char	*olddir, *newdir;
686 
687 	if ((olddir = encname(&mb, "", 0, protfile(old))) == NULL ||
688 			(newdir = encname(&mb, "", 0, protfile(new))) == NULL)
689 		return OKAY;
690 	if (rename(olddir, newdir) < 0) {
691 		perror(olddir);
692 		return STOP;
693 	}
694 	return OKAY;
695 }
696 
697 unsigned long
cached_uidvalidity(struct mailbox * mp)698 cached_uidvalidity(struct mailbox *mp)
699 {
700 	FILE	*uvfp;
701 	char	*uvname;
702 	unsigned long	uv;
703 
704 	if ((uvname = encname(mp, "UIDVALIDITY", 1, NULL)) == NULL)
705 		return 0;
706 	if ((uvfp = Fopen(uvname, "r")) == NULL ||
707 			(fcntl_lock(fileno(uvfp), F_RDLCK), 0) ||
708 			fscanf(uvfp, "%lu", &uv) != 1)
709 		uv = 0;
710 	Fclose(uvfp);
711 	return uv;
712 }
713 
714 static FILE *
cache_queue1(struct mailbox * mp,char * mode,char ** xname)715 cache_queue1(struct mailbox *mp, char *mode, char **xname)
716 {
717 	char	*name;
718 	FILE	*fp;
719 
720 	if ((name = encname(mp, "QUEUE", 0, NULL)) == NULL)
721 		return NULL;
722 	if ((fp = Fopen(name, mode)) != NULL)
723 		fcntl_lock(fileno(fp), F_WRLCK);
724 	if (xname)
725 		*xname = name;
726 	return fp;
727 }
728 
729 FILE *
cache_queue(struct mailbox * mp)730 cache_queue(struct mailbox *mp)
731 {
732 	FILE	*fp;
733 
734 	fp = cache_queue1(mp, "a", NULL);
735 	if (fp == NULL)
736 		fputs("Cannot queue IMAP command. Retry when online.\n",
737 				stderr);
738 	return fp;
739 }
740 
741 enum okay
cache_dequeue(struct mailbox * mp)742 cache_dequeue(struct mailbox *mp)
743 {
744 	int	bufsz;
745 	char	*cachedir, *eaccount, *buf, *oldbox;
746 	DIR	*dirfd;
747 	struct dirent	*dp;
748 
749 	if ((cachedir = value("imap-cache")) == NULL)
750 		return OKAY;
751 	cachedir = expand(cachedir);
752 	eaccount = strenc(mp->mb_imap_account);
753 	buf = salloc(bufsz = strlen(cachedir) + strlen(eaccount) + 2);
754 	snprintf(buf, bufsz, "%s/%s", cachedir, eaccount);
755 	if ((dirfd = opendir(buf)) == NULL)
756 		return OKAY;
757 	oldbox = mp->mb_imap_mailbox;
758 	while ((dp = readdir(dirfd)) != NULL) {
759 		if (dp->d_name[0] == '.')
760 			continue;
761 		mp->mb_imap_mailbox = strdec(dp->d_name);
762 		dequeue1(mp);
763 	}
764 	closedir(dirfd);
765 	mp->mb_imap_mailbox = oldbox;
766 	return OKAY;
767 }
768 
769 static enum okay
dequeue1(struct mailbox * mp)770 dequeue1(struct mailbox *mp)
771 {
772 	FILE	*fp = NULL, *uvfp = NULL;
773 	char	*qname, *uvname;
774 	unsigned long	uv;
775 	off_t	is_size;
776 	int	is_count;
777 
778 	fp = cache_queue1(mp, "r+", &qname);
779 	if (fp != NULL && fsize(fp) > 0) {
780 		if (imap_select(mp, &is_size, &is_count,
781 					mp->mb_imap_mailbox) != OKAY) {
782 			fprintf(stderr, "Cannot select \"%s\" for dequeuing.\n",
783 					mp->mb_imap_mailbox);
784 			goto save;
785 		}
786 		if ((uvname = encname(mp, "UIDVALIDITY", 0, NULL)) == NULL ||
787 				(uvfp = Fopen(uvname, "r")) == NULL ||
788 				(fcntl_lock(fileno(uvfp), F_RDLCK), 0) ||
789 				fscanf(uvfp, "%lu", &uv) != 1 ||
790 				uv != mp->mb_uidvalidity) {
791 			fprintf(stderr,
792 			"Unique identifiers for \"%s\" are out of date. "
793 				"Cannot commit IMAP commands.\n",
794 				mp->mb_imap_mailbox);
795 		save:	fputs("Saving IMAP commands to dead.letter\n", stderr);
796 			savedeadletter(fp);
797 			ftruncate(fileno(fp), 0);
798 			Fclose(fp);
799 			if (uvfp)
800 				Fclose(uvfp);
801 			return STOP;
802 		}
803 		Fclose(uvfp);
804 		printf("Committing IMAP commands for \"%s\"\n",
805 				mp->mb_imap_mailbox);
806 		imap_dequeue(mp, fp);
807 	}
808 	if (fp) {
809 		Fclose(fp);
810 		unlink(qname);
811 	}
812 	return OKAY;
813 }
814 #endif	/* HAVE_SOCKETS */
815