xref: /plan9/sys/src/cmd/upas/fs/planb.c (revision 21887c0b3fa083db468c32f3247dbe932cf708df)
1 /*
2  * Plan B (mail2fs) mail box format.
3  *
4  * BUG: this does not reconstruct the
5  * raw text for attachments.  So imap and others
6  * will be unable to access any attachment using upas/fs.
7  * As an aid, we add the path to the message directory
8  * to the message body, so the user could build the path
9  * for any attachment and open it.
10  */
11 
12 #include "common.h"
13 #include <ctype.h>
14 #include <plumb.h>
15 #include <libsec.h>
16 #include "dat.h"
17 
18 static int
readmessage(Message * m,char * msg)19 readmessage(Message *m, char *msg)
20 {
21 	int fd, i, n;
22 	char *buf, *name, *p;
23 	char hdr[128], sdigest[SHA1dlen*2+1];
24 	Dir *d;
25 
26 	buf = nil;
27 	d = nil;
28 	name = smprint("%s/raw", msg);
29 	if(name == nil)
30 		return -1;
31 	if(m->filename != nil)
32 		s_free(m->filename);
33 	m->filename = s_copy(name);
34 	fd = open(name, OREAD);
35 	if(fd < 0)
36 		goto Fail;
37 	n = read(fd, hdr, sizeof(hdr)-1);
38 	if(n <= 0)
39 		goto Fail;
40 	hdr[n] = 0;
41 	close(fd);
42 	fd = -1;
43 	p = strchr(hdr, '\n');
44 	if(p != nil)
45 		*++p = 0;
46 	if(strncmp(hdr, "From ", 5) != 0)
47 		goto Fail;
48 	free(name);
49 	name = smprint("%s/text", msg);
50 	if(name == nil)
51 		goto Fail;
52 	fd = open(name, OREAD);
53 	if(fd < 0)
54 		goto Fail;
55 	d = dirfstat(fd);
56 	if(d == nil)
57 		goto Fail;
58 	buf = malloc(strlen(hdr) + d->length + strlen(msg) + 10); /* few extra chars */
59 	if(buf == nil)
60 		goto Fail;
61 	strcpy(buf, hdr);
62 	p = buf+strlen(hdr);
63 	n = readn(fd, p, d->length);
64 	if(n < 0)
65 		goto Fail;
66 	sprint(p+n, "\n[%s]\n", msg);
67 	n += 2 + strlen(msg) + 2;
68 	close(fd);
69 	free(name);
70 	free(d);
71 	free(m->start);
72 	m->start = buf;
73 	m->lim = m->end = p+n;
74 	if(*(m->end-1) == '\n')
75 		m->end--;
76 	*m->end = 0;
77 	m->bend = m->rbend = m->end;
78 	sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
79 	for(i = 0; i < SHA1dlen; i++)
80 		sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
81 	m->sdigest = s_copy(sdigest);
82 	return 0;
83 Fail:
84 	if(fd >= 0)
85 		close(fd);
86 	free(name);
87 	free(buf);
88 	free(d);
89 	return -1;
90 }
91 
92 /*
93  * Deleted messages are kept as spam instead.
94  */
95 static void
archive(Message * m)96 archive(Message *m)
97 {
98 	char *dir, *p, *nname;
99 	Dir d;
100 
101 	dir = strdup(s_to_c(m->filename));
102 	nname = nil;
103 	if(dir == nil)
104 		return;
105 	p = strrchr(dir, '/');
106 	if(p == nil)
107 		goto Fail;
108 	*p = 0;
109 	p = strrchr(dir, '/');
110 	if(p == nil)
111 		goto Fail;
112 	p++;
113 	if(*p < '0' || *p > '9')
114 		goto Fail;
115 	nname = smprint("s.%s", p);
116 	if(nname == nil)
117 		goto Fail;
118 	nulldir(&d);
119 	d.name = nname;
120 	dirwstat(dir, &d);
121 Fail:
122 	free(dir);
123 	free(nname);
124 }
125 
126 int
purgembox(Mailbox * mb,int virtual)127 purgembox(Mailbox *mb, int virtual)
128 {
129 	Message *m, *next;
130 	int newdels;
131 
132 	/* forget about what's no longer in the mailbox */
133 	newdels = 0;
134 	for(m = mb->root->part; m != nil; m = next){
135 		next = m->next;
136 		if(m->deleted > 0 && m->refs == 0){
137 			if(m->inmbox){
138 				newdels++;
139 				/*
140 				 * virtual folders are virtual,
141 				 * we do not archive
142 				 */
143 				if(virtual == 0)
144 					archive(m);
145 			}
146 			delmessage(mb, m);
147 		}
148 	}
149 	return newdels;
150 }
151 
152 static int
mustshow(char * name)153 mustshow(char* name)
154 {
155 	if(isdigit(name[0]))
156 		return 1;
157 	if(0 && name[0] == 'a' && name[1] == '.')
158 		return 1;
159 	if(0 && name[0] == 's' && name[1] == '.')
160 		return 1;
161 	return 0;
162 }
163 
164 static int
readpbmessage(Mailbox * mb,char * msg,int doplumb)165 readpbmessage(Mailbox *mb, char *msg, int doplumb)
166 {
167 	Message *m, **l;
168 	char *x;
169 
170 	m = newmessage(mb->root);
171 	m->mallocd = 1;
172 	m->inmbox = 1;
173 	if(readmessage(m, msg) < 0){
174 		delmessage(mb, m);
175 		mb->root->subname--;
176 		return -1;
177 	}
178 	for(l = &mb->root->part; *l != nil; l = &(*l)->next)
179 		if(strcmp(s_to_c((*l)->filename), s_to_c(m->filename)) == 0 &&
180 		    *l != m){
181 			if((*l)->deleted < 0)
182 				(*l)->deleted = 0;
183 			delmessage(mb, m);
184 			mb->root->subname--;
185 			return -1;
186 		}
187 	x = strchr(m->start, '\n');
188 	if(x == nil)
189 		m->header = m->end;
190 	else
191 		m->header = x + 1;
192 	m->mheader = m->mhend = m->header;
193 	parseunix(m);
194 	parse(m, 0, mb, 0);
195 	logmsg("new", m);
196 
197 	/* chain in */
198 	*l = m;
199 	if(doplumb)
200 		mailplumb(mb, m, 0);
201 	return 0;
202 }
203 
204 static int
dcmp(Dir * a,Dir * b)205 dcmp(Dir *a, Dir *b)
206 {
207 	char *an, *bn;
208 
209 	an = a->name;
210 	bn = b->name;
211 	if(an[0] != 0 && an[1] == '.')
212 		an += 2;
213 	if(bn[0] != 0 && bn[1] == '.')
214 		bn += 2;
215 	return strcmp(an, bn);
216 }
217 
218 static void
readpbmbox(Mailbox * mb,int doplumb)219 readpbmbox(Mailbox *mb, int doplumb)
220 {
221 	int fd, i, j, nd, nmd;
222 	char *month, *msg;
223 	Dir *d, *md;
224 
225 	fd = open(mb->path, OREAD);
226 	if(fd < 0){
227 		fprint(2, "%s: %s: %r\n", argv0, mb->path);
228 		return;
229 	}
230 	nd = dirreadall(fd, &d);
231 	close(fd);
232 	if(nd > 0)
233 		qsort(d, nd, sizeof d[0], (int (*)(void*, void*))dcmp);
234 	for(i = 0; i < nd; i++){
235 		month = smprint("%s/%s", mb->path, d[i].name);
236 		if(month == nil)
237 			break;
238 		fd = open(month, OREAD);
239 		if(fd < 0){
240 			fprint(2, "%s: %s: %r\n", argv0, month);
241 			free(month);
242 			continue;
243 		}
244 		md = dirfstat(fd);
245 		if(md != nil && (md->qid.type & QTDIR) != 0){
246 			free(md);
247 			md = nil;
248 			nmd = dirreadall(fd, &md);
249 			for(j = 0; j < nmd; j++)
250 				if(mustshow(md[j].name)){
251 					msg = smprint("%s/%s", month, md[j].name);
252 					readpbmessage(mb, msg, doplumb);
253 					free(msg);
254 				}
255 		}
256 		close(fd);
257 		free(month);
258 		free(md);
259 		md = nil;
260 	}
261 	free(d);
262 }
263 
264 static void
readpbvmbox(Mailbox * mb,int doplumb)265 readpbvmbox(Mailbox *mb, int doplumb)
266 {
267 	int fd, nr;
268 	long sz;
269 	char *data, *ln, *p, *nln, *msg;
270 	Dir *d;
271 
272 	fd = open(mb->path, OREAD);
273 	if(fd < 0){
274 		fprint(2, "%s: %s: %r\n", argv0, mb->path);
275 		return;
276 	}
277 	d = dirfstat(fd);
278 	if(d == nil){
279 		fprint(2, "%s: %s: %r\n", argv0, mb->path);
280 		close(fd);
281 		return;
282 	}
283 	sz = d->length;
284 	free(d);
285 	if(sz > 2 * 1024 * 1024){
286 		sz = 2 * 1024 * 1024;
287 		fprint(2, "%s: %s: bug: folder too big\n", argv0, mb->path);
288 	}
289 	data = malloc(sz+1);
290 	if(data == nil){
291 		close(fd);
292 		fprint(2, "%s: no memory\n", argv0);
293 		return;
294 	}
295 	nr = readn(fd, data, sz);
296 	close(fd);
297 	if(nr < 0){
298 		fprint(2, "%s: %s: %r\n", argv0, mb->path);
299 		free(data);
300 		return;
301 	}
302 	data[nr] = 0;
303 
304 	for(ln = data; *ln != 0; ln = nln){
305 		nln = strchr(ln, '\n');
306 		if(nln != nil)
307 			*nln++ = 0;
308 		else
309 			nln = ln + strlen(ln);
310 		p = strchr(ln , ' ');
311 		if(p != nil)
312 			*p = 0;
313 		p = strchr(ln, '\t');
314 		if(p != nil)
315 			*p = 0;
316 		p = strstr(ln, "/text");
317 		if(p != nil)
318 			*p = 0;
319 		msg = smprint("/mail/box/%s/msgs/%s", user, ln);
320 		if(msg == nil){
321 			fprint(2, "%s: no memory\n", argv0);
322 			continue;
323 		}
324 		readpbmessage(mb, msg, doplumb);
325 		free(msg);
326 	}
327 	free(data);
328 }
329 
330 static char*
readmbox(Mailbox * mb,int doplumb,int virt)331 readmbox(Mailbox *mb, int doplumb, int virt)
332 {
333 	int fd;
334 	Dir *d;
335 	Message *m;
336 	static char err[Errlen];
337 
338 	if(debug)
339 		fprint(2, "read mbox %s\n", mb->path);
340 	fd = open(mb->path, OREAD);
341 	if(fd < 0){
342 		errstr(err, sizeof(err));
343 		return err;
344 	}
345 
346 	d = dirfstat(fd);
347 	if(d == nil){
348 		close(fd);
349 		errstr(err, sizeof(err));
350 		return err;
351 	}
352 	if(mb->d != nil){
353 		if(d->qid.path == mb->d->qid.path &&
354 		   d->qid.vers == mb->d->qid.vers){
355 			close(fd);
356 			free(d);
357 			return nil;
358 		}
359 		free(mb->d);
360 	}
361 	close(fd);
362 	mb->d = d;
363 	mb->vers++;
364 	henter(PATH(0, Qtop), mb->name,
365 		(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
366 	snprint(err, sizeof err, "reading '%s'", mb->path);
367 	logmsg(err, nil);
368 
369 	for(m = mb->root->part; m != nil; m = m->next)
370 		if(m->deleted == 0)
371 			m->deleted = -1;
372 	if(virt == 0)
373 		readpbmbox(mb, doplumb);
374 	else
375 		readpbvmbox(mb, doplumb);
376 
377 	/*
378 	 * messages removed from the mbox; flag them to go.
379 	 */
380 	for(m = mb->root->part; m != nil; m = m->next)
381 		if(m->deleted < 0 && doplumb){
382 			m->inmbox = 0;
383 			m->deleted = 1;
384 			mailplumb(mb, m, 1);
385 		}
386 	logmsg("mbox read", nil);
387 	return nil;
388 }
389 
390 static char*
mbsync(Mailbox * mb,int doplumb)391 mbsync(Mailbox *mb, int doplumb)
392 {
393 	char *rv;
394 
395 	rv = readmbox(mb, doplumb, 0);
396 	purgembox(mb, 0);
397 	return rv;
398 }
399 
400 static char*
mbvsync(Mailbox * mb,int doplumb)401 mbvsync(Mailbox *mb, int doplumb)
402 {
403 	char *rv;
404 
405 	rv = readmbox(mb, doplumb, 1);
406 	purgembox(mb, 1);
407 	return rv;
408 }
409 
410 char*
planbmbox(Mailbox * mb,char * path)411 planbmbox(Mailbox *mb, char *path)
412 {
413 	char *list;
414 	static char err[64];
415 
416 	if(access(path, AEXIST) < 0)
417 		return Enotme;
418 	list = smprint("%s/list", path);
419 	if(access(list, AEXIST) < 0){
420 		free(list);
421 		return Enotme;
422 	}
423 	free(list);
424 	mb->sync = mbsync;
425 	if(debug)
426 		fprint(2, "planb mbox %s\n", path);
427 	return nil;
428 }
429 
430 char*
planbvmbox(Mailbox * mb,char * path)431 planbvmbox(Mailbox *mb, char *path)
432 {
433 	int fd, nr, i;
434 	char buf[64];
435 	static char err[64];
436 
437 	fd = open(path, OREAD);
438 	if(fd < 0)
439 		return Enotme;
440 	nr = read(fd, buf, sizeof(buf)-1);
441 	close(fd);
442 	if(nr < 7)
443 		return Enotme;
444 	buf[nr] = 0;
445 	for(i = 0; i < 6; i++)
446 		if(buf[i] < '0' || buf[i] > '9')
447 			return Enotme;
448 	if(buf[6] != '/')
449 		return Enotme;
450 	mb->sync = mbvsync;
451 	if(debug)
452 		fprint(2, "planb virtual mbox %s\n", path);
453 	return nil;
454 }
455