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 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 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 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* 165 _readmbox(Mailbox *mb, int doplumb, Mlock *lk) 166 { 167 int fd; 168 String *tmp; 169 Dir *d; 170 static char err[128]; 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 retry: 181 fd = open(mb->path, OREAD); 182 if(fd < 0){ 183 errstr(err, sizeof(err)); 184 if(strstr(err, "exist") != 0){ 185 tmp = s_copy(mb->path); 186 s_append(tmp, ".tmp"); 187 if(sysrename(s_to_c(tmp), mb->path) == 0){ 188 s_free(tmp); 189 goto retry; 190 } 191 s_free(tmp); 192 } 193 return err; 194 } 195 196 /* 197 * a new qid.path means reread the mailbox, while 198 * a new qid.vers means read any new messages 199 */ 200 d = dirfstat(fd); 201 if(d == nil){ 202 close(fd); 203 errstr(err, sizeof(err)); 204 return err; 205 } 206 if(mb->d != nil){ 207 if(d->qid.path == mb->d->qid.path && d->qid.vers == mb->d->qid.vers){ 208 close(fd); 209 free(d); 210 return nil; 211 } 212 if(d->qid.path == mb->d->qid.path){ 213 while(*l != nil) 214 l = &(*l)->next; 215 seek(fd, mb->d->length, 0); 216 } 217 free(mb->d); 218 } 219 mb->d = d; 220 mb->vers++; 221 henter(PATH(0, Qtop), mb->name, 222 (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb); 223 224 inb = emalloc(sizeof(Inbuf)); 225 inb->rptr = inb->wptr = inb->data; 226 inb->fd = fd; 227 228 // read new messages 229 snprint(err, sizeof err, "reading '%s'", mb->path); 230 logmsg(err, nil); 231 for(;;){ 232 if(lk != nil) 233 syslockrefresh(lk); 234 m = newmessage(mb->root); 235 m->mallocd = 1; 236 m->inmbox = 1; 237 if(readmessage(m, inb) < 0){ 238 delmessage(mb, m); 239 mb->root->subname--; 240 break; 241 } 242 243 // merge mailbox versions 244 while(*l != nil){ 245 if(memcmp((*l)->digest, m->digest, SHA1dlen) == 0){ 246 // matches mail we already read, discard 247 logmsg("duplicate", *l); 248 delmessage(mb, m); 249 mb->root->subname--; 250 m = nil; 251 l = &(*l)->next; 252 break; 253 } else { 254 // old mail no longer in box, mark deleted 255 logmsg("disappeared", *l); 256 if(doplumb) 257 mailplumb(mb, *l, 1); 258 (*l)->inmbox = 0; 259 (*l)->deleted = 1; 260 l = &(*l)->next; 261 } 262 } 263 if(m == nil) 264 continue; 265 266 x = strchr(m->start, '\n'); 267 if(x == nil) 268 m->header = m->end; 269 else 270 m->header = x + 1; 271 m->mheader = m->mhend = m->header; 272 parseunix(m); 273 parse(m, 0, mb, 0); 274 logmsg("new", m); 275 276 /* chain in */ 277 *l = m; 278 l = &m->next; 279 if(doplumb) 280 mailplumb(mb, m, 0); 281 282 } 283 logmsg("mbox read", nil); 284 285 // whatever is left has been removed from the mbox, mark deleted 286 while(*l != nil){ 287 if(doplumb) 288 mailplumb(mb, *l, 1); 289 (*l)->inmbox = 0; 290 (*l)->deleted = 1; 291 l = &(*l)->next; 292 } 293 294 close(fd); 295 free(inb); 296 return nil; 297 } 298 299 static void 300 _writembox(Mailbox *mb, Mlock *lk) 301 { 302 Dir *d; 303 Message *m; 304 String *tmp; 305 int mode, errs; 306 Biobuf *b; 307 308 tmp = s_copy(mb->path); 309 s_append(tmp, ".tmp"); 310 311 /* 312 * preserve old files permissions, if possible 313 */ 314 d = dirstat(mb->path); 315 if(d != nil){ 316 mode = d->mode&0777; 317 free(d); 318 } else 319 mode = MBOXMODE; 320 321 sysremove(s_to_c(tmp)); 322 b = sysopen(s_to_c(tmp), "alc", mode); 323 if(b == 0){ 324 fprint(2, "can't write temporary mailbox %s: %r\n", s_to_c(tmp)); 325 return; 326 } 327 328 logmsg("writing new mbox", nil); 329 errs = 0; 330 for(m = mb->root->part; m != nil; m = m->next){ 331 if(lk != nil) 332 syslockrefresh(lk); 333 if(m->deleted) 334 continue; 335 logmsg("writing", m); 336 if(Bwrite(b, m->start, m->end - m->start) < 0) 337 errs = 1; 338 if(Bwrite(b, "\n", 1) < 0) 339 errs = 1; 340 } 341 logmsg("wrote new mbox", nil); 342 343 if(sysclose(b) < 0) 344 errs = 1; 345 346 if(errs){ 347 fprint(2, "error writing temporary mail file\n"); 348 s_free(tmp); 349 return; 350 } 351 352 sysremove(mb->path); 353 if(sysrename(s_to_c(tmp), mb->path) < 0) 354 fprint(2, "%s: can't rename %s to %s: %r\n", argv0, 355 s_to_c(tmp), mb->path); 356 s_free(tmp); 357 if(mb->d != nil) 358 free(mb->d); 359 mb->d = dirstat(mb->path); 360 } 361 362 char* 363 plan9syncmbox(Mailbox *mb, int doplumb) 364 { 365 Mlock *lk; 366 char *rv; 367 368 lk = nil; 369 if(mb->dolock){ 370 lk = syslock(mb->path); 371 if(lk == nil) 372 return "can't lock mailbox"; 373 } 374 375 rv = _readmbox(mb, doplumb, lk); /* interpolate */ 376 if(purgedeleted(mb) > 0) 377 _writembox(mb, lk); 378 379 if(lk != nil) 380 sysunlock(lk); 381 382 return rv; 383 } 384 385 // 386 // look to see if we can open this mail box 387 // 388 char* 389 plan9mbox(Mailbox *mb, char *path) 390 { 391 static char err[64]; 392 String *tmp; 393 394 if(access(path, AEXIST) < 0){ 395 errstr(err, sizeof(err)); 396 tmp = s_copy(path); 397 s_append(tmp, ".tmp"); 398 if(access(s_to_c(tmp), AEXIST) < 0){ 399 s_free(tmp); 400 return err; 401 } 402 s_free(tmp); 403 } 404 405 mb->sync = plan9syncmbox; 406 return nil; 407 } 408