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