1 #include "common.h"
2 #include <ctype.h>
3 #include <plumb.h>
4 #include <libsec.h>
5 #include "dat.h"
6
7 enum {
8 Buffersize = 64*1024,
9 };
10
11 typedef struct Inbuf Inbuf;
12 struct Inbuf
13 {
14 int fd;
15 uchar *lim;
16 uchar *rptr;
17 uchar *wptr;
18 uchar data[Buffersize+7];
19 };
20
21 static void
addtomessage(Message * m,uchar * p,int n,int done)22 addtomessage(Message *m, uchar *p, int n, int done)
23 {
24 int i, len;
25
26 // add to message (+1 in malloc is for a trailing NUL)
27 if(m->lim - m->end < n){
28 if(m->start != nil){
29 i = m->end-m->start;
30 if(done)
31 len = i + n;
32 else
33 len = (4*(i+n))/3;
34 m->start = erealloc(m->start, len + 1);
35 m->end = m->start + i;
36 } else {
37 if(done)
38 len = n;
39 else
40 len = 2*n;
41 m->start = emalloc(len + 1);
42 m->end = m->start;
43 }
44 m->lim = m->start + len;
45 *m->lim = '\0';
46 }
47
48 memmove(m->end, p, n);
49 m->end += n;
50 *m->end = '\0';
51 }
52
53 //
54 // read in a single message
55 //
56 static int
readmessage(Message * m,Inbuf * inb)57 readmessage(Message *m, Inbuf *inb)
58 {
59 int i, n, done;
60 uchar *p, *np;
61 char sdigest[SHA1dlen*2+1];
62 char tmp[64];
63
64 for(done = 0; !done;){
65 n = inb->wptr - inb->rptr;
66 if(n < 6){
67 if(n)
68 memmove(inb->data, inb->rptr, n);
69 inb->rptr = inb->data;
70 inb->wptr = inb->rptr + n;
71 i = read(inb->fd, inb->wptr, Buffersize);
72 if(i < 0){
73 if(fd2path(inb->fd, tmp, sizeof tmp) < 0)
74 strcpy(tmp, "unknown mailbox");
75 fprint(2, "error reading '%s': %r\n", tmp);
76 return -1;
77 }
78 if(i == 0){
79 if(n != 0)
80 addtomessage(m, inb->rptr, n, 1);
81 if(m->end == m->start)
82 return -1;
83 break;
84 }
85 inb->wptr += i;
86 }
87
88 // look for end of message
89 for(p = inb->rptr; p < inb->wptr; p = np+1){
90 // first part of search for '\nFrom '
91 np = memchr(p, '\n', inb->wptr - p);
92 if(np == nil){
93 p = inb->wptr;
94 break;
95 }
96
97 /*
98 * if we've found a \n but there's
99 * not enough room for '\nFrom ', don't do
100 * the comparison till we've read in more.
101 */
102 if(inb->wptr - np < 6){
103 p = np;
104 break;
105 }
106
107 if(strncmp((char*)np, "\nFrom ", 6) == 0){
108 done = 1;
109 p = np+1;
110 break;
111 }
112 }
113
114 // add to message (+ 1 in malloc is for a trailing null)
115 n = p - inb->rptr;
116 addtomessage(m, inb->rptr, n, done);
117 inb->rptr += n;
118 }
119
120 // if it doesn't start with a 'From ', this ain't a mailbox
121 if(strncmp(m->start, "From ", 5) != 0)
122 return -1;
123
124 // dump trailing newline, make sure there's a trailing null
125 // (helps in body searches)
126 if(*(m->end-1) == '\n')
127 m->end--;
128 *m->end = 0;
129 m->bend = m->rbend = m->end;
130
131 // digest message
132 sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
133 for(i = 0; i < SHA1dlen; i++)
134 sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
135 m->sdigest = s_copy(sdigest);
136
137 return 0;
138 }
139
140
141 // throw out deleted messages. return number of freshly deleted messages
142 int
purgedeleted(Mailbox * mb)143 purgedeleted(Mailbox *mb)
144 {
145 Message *m, *next;
146 int newdels;
147
148 // forget about what's no longer in the mailbox
149 newdels = 0;
150 for(m = mb->root->part; m != nil; m = next){
151 next = m->next;
152 if(m->deleted && m->refs == 0){
153 if(m->inmbox)
154 newdels++;
155 delmessage(mb, m);
156 }
157 }
158 return newdels;
159 }
160
161 //
162 // read in the mailbox and parse into messages.
163 //
164 static char*
_readmbox(Mailbox * mb,int doplumb,Mlock * lk)165 _readmbox(Mailbox *mb, int doplumb, Mlock *lk)
166 {
167 int fd, n;
168 String *tmp;
169 Dir *d;
170 static char err[Errlen];
171 Message *m, **l;
172 Inbuf *inb;
173 char *x;
174
175 l = &mb->root->part;
176
177 /*
178 * open the mailbox. If it doesn't exist, try the temporary one.
179 */
180 n = 0;
181 retry:
182 fd = open(mb->path, OREAD);
183 if(fd < 0){
184 rerrstr(err, sizeof(err));
185 if(strstr(err, "exclusive lock") != 0 && n++ < 20){
186 sleep(500); /* wait for lock to go away */
187 goto retry;
188 }
189 if(strstr(err, "exist") != 0){
190 tmp = s_copy(mb->path);
191 s_append(tmp, ".tmp");
192 if(sysrename(s_to_c(tmp), mb->path) == 0){
193 s_free(tmp);
194 goto retry;
195 }
196 s_free(tmp);
197 }
198 return err;
199 }
200
201 /*
202 * a new qid.path means reread the mailbox, while
203 * a new qid.vers means read any new messages
204 */
205 d = dirfstat(fd);
206 if(d == nil){
207 close(fd);
208 errstr(err, sizeof(err));
209 return err;
210 }
211 if(mb->d != nil){
212 if(d->qid.path == mb->d->qid.path && d->qid.vers == mb->d->qid.vers){
213 close(fd);
214 free(d);
215 return nil;
216 }
217 if(d->qid.path == mb->d->qid.path){
218 while(*l != nil)
219 l = &(*l)->next;
220 seek(fd, mb->d->length, 0);
221 }
222 free(mb->d);
223 }
224 mb->d = d;
225 mb->vers++;
226 henter(PATH(0, Qtop), mb->name,
227 (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
228
229 inb = emalloc(sizeof(Inbuf));
230 inb->rptr = inb->wptr = inb->data;
231 inb->fd = fd;
232
233 // read new messages
234 snprint(err, sizeof err, "reading '%s'", mb->path);
235 logmsg(err, nil);
236 for(;;){
237 if(lk != nil)
238 syslockrefresh(lk);
239 m = newmessage(mb->root);
240 m->mallocd = 1;
241 m->inmbox = 1;
242 if(readmessage(m, inb) < 0){
243 delmessage(mb, m);
244 mb->root->subname--;
245 break;
246 }
247
248 // merge mailbox versions
249 while(*l != nil){
250 if(memcmp((*l)->digest, m->digest, SHA1dlen) == 0){
251 // matches mail we already read, discard
252 logmsg("duplicate", *l);
253 delmessage(mb, m);
254 mb->root->subname--;
255 m = nil;
256 l = &(*l)->next;
257 break;
258 } else {
259 // old mail no longer in box, mark deleted
260 logmsg("disappeared", *l);
261 if(doplumb)
262 mailplumb(mb, *l, 1);
263 (*l)->inmbox = 0;
264 (*l)->deleted = 1;
265 l = &(*l)->next;
266 }
267 }
268 if(m == nil)
269 continue;
270
271 x = strchr(m->start, '\n');
272 if(x == nil)
273 m->header = m->end;
274 else
275 m->header = x + 1;
276 m->mheader = m->mhend = m->header;
277 parseunix(m);
278 parse(m, 0, mb, 0);
279 logmsg("new", m);
280
281 /* chain in */
282 *l = m;
283 l = &m->next;
284 if(doplumb)
285 mailplumb(mb, m, 0);
286
287 }
288 logmsg("mbox read", nil);
289
290 // whatever is left has been removed from the mbox, mark deleted
291 while(*l != nil){
292 if(doplumb)
293 mailplumb(mb, *l, 1);
294 (*l)->inmbox = 0;
295 (*l)->deleted = 1;
296 l = &(*l)->next;
297 }
298
299 close(fd);
300 free(inb);
301 return nil;
302 }
303
304 static void
_writembox(Mailbox * mb,Mlock * lk)305 _writembox(Mailbox *mb, Mlock *lk)
306 {
307 Dir *d;
308 Message *m;
309 String *tmp;
310 int mode, errs;
311 Biobuf *b;
312
313 tmp = s_copy(mb->path);
314 s_append(tmp, ".tmp");
315
316 /*
317 * preserve old files permissions, if possible
318 */
319 d = dirstat(mb->path);
320 if(d != nil){
321 mode = d->mode&0777;
322 free(d);
323 } else
324 mode = MBOXMODE;
325
326 sysremove(s_to_c(tmp));
327 b = sysopen(s_to_c(tmp), "alc", mode);
328 if(b == 0){
329 fprint(2, "can't write temporary mailbox %s: %r\n", s_to_c(tmp));
330 return;
331 }
332
333 logmsg("writing new mbox", nil);
334 errs = 0;
335 for(m = mb->root->part; m != nil; m = m->next){
336 if(lk != nil)
337 syslockrefresh(lk);
338 if(m->deleted)
339 continue;
340 logmsg("writing", m);
341 if(Bwrite(b, m->start, m->end - m->start) < 0)
342 errs = 1;
343 if(Bwrite(b, "\n", 1) < 0)
344 errs = 1;
345 }
346 logmsg("wrote new mbox", nil);
347
348 if(sysclose(b) < 0)
349 errs = 1;
350
351 if(errs){
352 fprint(2, "error writing temporary mail file\n");
353 s_free(tmp);
354 return;
355 }
356
357 sysremove(mb->path);
358 if(sysrename(s_to_c(tmp), mb->path) < 0)
359 fprint(2, "%s: can't rename %s to %s: %r\n", argv0,
360 s_to_c(tmp), mb->path);
361 s_free(tmp);
362 if(mb->d != nil)
363 free(mb->d);
364 mb->d = dirstat(mb->path);
365 }
366
367 char*
plan9syncmbox(Mailbox * mb,int doplumb)368 plan9syncmbox(Mailbox *mb, int doplumb)
369 {
370 Mlock *lk;
371 char *rv;
372
373 lk = nil;
374 if(mb->dolock){
375 lk = syslock(mb->path);
376 if(lk == nil)
377 return "can't lock mailbox";
378 }
379
380 rv = _readmbox(mb, doplumb, lk); /* interpolate */
381 if(purgedeleted(mb) > 0)
382 _writembox(mb, lk);
383
384 if(lk != nil)
385 sysunlock(lk);
386
387 return rv;
388 }
389
390 //
391 // look to see if we can open this mail box
392 //
393 char*
plan9mbox(Mailbox * mb,char * path)394 plan9mbox(Mailbox *mb, char *path)
395 {
396 static char err[Errlen];
397 String *tmp;
398
399 if(access(path, AEXIST) < 0){
400 errstr(err, sizeof(err));
401 tmp = s_copy(path);
402 s_append(tmp, ".tmp");
403 if(access(s_to_c(tmp), AEXIST) < 0){
404 s_free(tmp);
405 return err;
406 }
407 s_free(tmp);
408 }
409
410 mb->sync = plan9syncmbox;
411 return nil;
412 }
413