mail.c (13406B)
1 #include <u.h> 2 #include <libc.h> 3 #include <bio.h> 4 #include <thread.h> 5 #include <9pclient.h> 6 #include <plumb.h> 7 #include <ctype.h> 8 #include "dat.h" 9 10 char *maildir = "Mail/"; /* mountpoint of mail file system */ 11 char *mboxname = "mbox"; /* mailboxdir/mboxname is mail spool file */ 12 char *mailboxdir = nil; /* nil == /mail/box/$user */ 13 char *fsname; /* filesystem for mailboxdir/mboxname is at maildir/fsname */ 14 char *user; 15 char *outgoing; 16 char *srvname; 17 18 Window *wbox; 19 Message mbox; 20 Message replies; 21 char *home; 22 CFid *plumbsendfd; 23 CFid *plumbseemailfd; 24 CFid *plumbshowmailfd; 25 CFid *plumbsendmailfd; 26 Channel *cplumb; 27 Channel *cplumbshow; 28 Channel *cplumbsend; 29 int wctlfd; 30 void mainctl(void*); 31 void plumbproc(void*); 32 void plumbshowproc(void*); 33 void plumbsendproc(void*); 34 void plumbthread(void); 35 void plumbshowthread(void*); 36 void plumbsendthread(void*); 37 38 int shortmenu; 39 40 CFsys *mailfs; 41 CFsys *acmefs; 42 43 void 44 usage(void) 45 { 46 fprint(2, "usage: Mail [-sS] [-n srvname] [-o outgoing] [mailboxname [directoryname]]\n"); 47 threadexitsall("usage"); 48 } 49 50 void 51 removeupasfs(void) 52 { 53 char buf[256]; 54 55 if(strcmp(mboxname, "mbox") == 0) 56 return; 57 snprint(buf, sizeof buf, "close %s", mboxname); 58 fswrite(mbox.ctlfd, buf, strlen(buf)); 59 } 60 61 int 62 ismaildir(char *s) 63 { 64 Dir *d; 65 int ret; 66 67 d = fsdirstat(mailfs, s); 68 if(d == nil) 69 return 0; 70 ret = d->qid.type & QTDIR; 71 free(d); 72 return ret; 73 } 74 75 void 76 threadmain(int argc, char *argv[]) 77 { 78 char *s, *name; 79 char err[ERRMAX], *cmd; 80 int i, newdir; 81 Fmt fmt; 82 83 doquote = needsrcquote; 84 quotefmtinstall(); 85 86 /* open these early so we won't miss notification of new mail messages while we read mbox */ 87 if((plumbsendfd = plumbopenfid("send", OWRITE|OCEXEC)) == nil) 88 fprint(2, "warning: open plumb/send: %r\n"); 89 if((plumbseemailfd = plumbopenfid("seemail", OREAD|OCEXEC)) == nil) 90 fprint(2, "warning: open plumb/seemail: %r\n"); 91 if((plumbshowmailfd = plumbopenfid("showmail", OREAD|OCEXEC)) == nil) 92 fprint(2, "warning: open plumb/showmail: %r\n"); 93 94 shortmenu = 0; 95 srvname = "mail"; 96 ARGBEGIN{ 97 case 's': 98 shortmenu = 1; 99 break; 100 case 'S': 101 shortmenu = 2; 102 break; 103 case 'o': 104 outgoing = EARGF(usage()); 105 break; 106 case 'm': 107 smprint(maildir, "%s/", EARGF(usage())); 108 break; 109 case 'n': 110 srvname = EARGF(usage()); 111 break; 112 default: 113 usage(); 114 }ARGEND 115 116 acmefs = nsmount("acme",nil); 117 if(acmefs == nil) 118 error("cannot mount acme: %r"); 119 mailfs = nsmount(srvname, nil); 120 if(mailfs == nil) 121 error("cannot mount %s: %r", srvname); 122 123 name = "mbox"; 124 125 newdir = 1; 126 if(argc > 0){ 127 i = strlen(argv[0]); 128 if(argc>2 || i==0) 129 usage(); 130 /* see if the name is that of an existing /mail/fs directory */ 131 if(argc==1 && argv[0][0] != '/' && ismaildir(argv[0])){ 132 name = argv[0]; 133 mboxname = estrdup(name); 134 newdir = 0; 135 }else{ 136 if(argv[0][i-1] == '/') 137 argv[0][i-1] = '\0'; 138 s = strrchr(argv[0], '/'); 139 if(s == nil) 140 mboxname = estrdup(argv[0]); 141 else{ 142 *s++ = '\0'; 143 if(*s == '\0') 144 usage(); 145 mailboxdir = argv[0]; 146 mboxname = estrdup(s); 147 } 148 if(argc > 1) 149 name = argv[1]; 150 else 151 name = mboxname; 152 } 153 } 154 155 user = getenv("user"); 156 if(user == nil) 157 user = "none"; 158 home = getenv("home"); 159 if(home == nil) 160 home = getenv("HOME"); 161 if(home == nil) 162 error("can't find $home"); 163 if(mailboxdir == nil) 164 mailboxdir = estrstrdup(home, "/mail"); 165 if(outgoing == nil) 166 outgoing = estrstrdup(mailboxdir, "/outgoing"); 167 168 mbox.ctlfd = fsopen(mailfs, estrstrdup(mboxname, "/ctl"), OWRITE); 169 if(mbox.ctlfd == nil) 170 error("can't open %s: %r", estrstrdup(mboxname, "/ctl")); 171 172 fsname = estrdup(name); 173 if(newdir && argc > 0){ 174 s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1); 175 for(i=0; i<10; i++){ 176 sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname); 177 if(fswrite(mbox.ctlfd, s, strlen(s)) >= 0) 178 break; 179 err[0] = '\0'; 180 errstr(err, sizeof err); 181 if(strstr(err, "mbox name in use") == nil) 182 error("can't create directory %s for mail: %s", name, err); 183 free(fsname); 184 fsname = emalloc(strlen(name)+10); 185 sprint(fsname, "%s-%d", name, i); 186 } 187 if(i == 10) 188 error("can't open %s/%s: %r", mailboxdir, mboxname); 189 free(s); 190 } 191 192 s = estrstrdup(fsname, "/"); 193 mbox.name = estrstrdup(maildir, s); 194 mbox.level= 0; 195 readmbox(&mbox, maildir, s); 196 home = getenv("home"); 197 if(home == nil) 198 home = "/"; 199 200 wbox = newwindow(); 201 winname(wbox, mbox.name); 202 wintagwrite(wbox, "Put Mail Delmesg ", 3+1+4+1+7+1); 203 threadcreate(mainctl, wbox, STACK); 204 205 fmtstrinit(&fmt); 206 fmtprint(&fmt, "Mail"); 207 if(shortmenu) 208 fmtprint(&fmt, " -%c", "sS"[shortmenu-1]); 209 if(outgoing) 210 fmtprint(&fmt, " -o %s", outgoing); 211 fmtprint(&fmt, " %s", name); 212 cmd = fmtstrflush(&fmt); 213 if(cmd == nil) 214 sysfatal("out of memory"); 215 winsetdump(wbox, "/acme/mail", cmd); 216 mbox.w = wbox; 217 218 mesgmenu(wbox, &mbox); 219 winclean(wbox); 220 221 /* wctlfd = open("/dev/wctl", OWRITE|OCEXEC); /* for acme window */ 222 wctlfd = -1; 223 cplumb = chancreate(sizeof(Plumbmsg*), 0); 224 cplumbshow = chancreate(sizeof(Plumbmsg*), 0); 225 if(strcmp(name, "mbox") == 0){ 226 /* 227 * Avoid creating multiple windows to send mail by only accepting 228 * sendmail plumb messages if we're reading the main mailbox. 229 */ 230 plumbsendmailfd = plumbopenfid("sendmail", OREAD|OCEXEC); 231 cplumbsend = chancreate(sizeof(Plumbmsg*), 0); 232 proccreate(plumbsendproc, nil, STACK); 233 threadcreate(plumbsendthread, nil, STACK); 234 } 235 /* start plumb reader as separate proc ... */ 236 proccreate(plumbproc, nil, STACK); 237 proccreate(plumbshowproc, nil, STACK); 238 threadcreate(plumbshowthread, nil, STACK); 239 fswrite(mbox.ctlfd, "refresh", 7); 240 /* ... and use this thread to read the messages */ 241 plumbthread(); 242 } 243 244 void 245 plumbproc(void* v) 246 { 247 Plumbmsg *m; 248 249 threadsetname("plumbproc"); 250 for(;;){ 251 m = plumbrecvfid(plumbseemailfd); 252 sendp(cplumb, m); 253 if(m == nil) 254 threadexits(nil); 255 } 256 } 257 258 void 259 plumbshowproc(void* v) 260 { 261 Plumbmsg *m; 262 263 threadsetname("plumbshowproc"); 264 for(;;){ 265 m = plumbrecvfid(plumbshowmailfd); 266 sendp(cplumbshow, m); 267 if(m == nil) 268 threadexits(nil); 269 } 270 } 271 272 void 273 plumbsendproc(void* v) 274 { 275 Plumbmsg *m; 276 277 threadsetname("plumbsendproc"); 278 for(;;){ 279 m = plumbrecvfid(plumbsendmailfd); 280 sendp(cplumbsend, m); 281 if(m == nil) 282 threadexits(nil); 283 } 284 } 285 286 void 287 newmesg(char *name, char *digest) 288 { 289 Dir *d; 290 291 if(strncmp(name, mbox.name, strlen(mbox.name)) != 0) 292 return; /* message is about another mailbox */ 293 if(mesglookupfile(&mbox, name, digest) != nil) 294 return; 295 if(strncmp(name, "Mail/", 5) == 0) 296 name += 5; 297 d = fsdirstat(mailfs, name); 298 if(d == nil) 299 return; 300 if(mesgadd(&mbox, mbox.name, d, digest)) 301 mesgmenunew(wbox, &mbox); 302 free(d); 303 } 304 305 void 306 showmesg(char *name, char *digest) 307 { 308 char *n; 309 char *mb; 310 311 mb = mbox.name; 312 if(strncmp(name, mb, strlen(mb)) != 0) 313 return; /* message is about another mailbox */ 314 n = estrdup(name+strlen(mb)); 315 if(n[strlen(n)-1] != '/') 316 n = egrow(n, "/", nil); 317 mesgopen(&mbox, mbox.name, name+strlen(mb), nil, 1, digest); 318 free(n); 319 } 320 321 void 322 delmesg(char *name, char *digest, int dodel, char *save) 323 { 324 Message *m; 325 326 m = mesglookupfile(&mbox, name, digest); 327 if(m != nil){ 328 if(save) 329 mesgcommand(m, estrstrdup("Save ", save)); 330 if(dodel) 331 mesgmenumarkdel(wbox, &mbox, m, 1); 332 else{ 333 /* notification came from plumber - message is gone */ 334 mesgmenudel(wbox, &mbox, m); 335 if(!m->opened) 336 mesgdel(&mbox, m); 337 } 338 } 339 } 340 341 void 342 plumbthread(void) 343 { 344 Plumbmsg *m; 345 Plumbattr *a; 346 char *type, *digest; 347 348 threadsetname("plumbthread"); 349 while((m = recvp(cplumb)) != nil){ 350 a = m->attr; 351 digest = plumblookup(a, "digest"); 352 type = plumblookup(a, "mailtype"); 353 if(type == nil) 354 fprint(2, "Mail: plumb message with no mailtype attribute\n"); 355 else if(strcmp(type, "new") == 0) 356 newmesg(m->data, digest); 357 else if(strcmp(type, "delete") == 0) 358 delmesg(m->data, digest, 0, nil); 359 else 360 fprint(2, "Mail: unknown plumb attribute %s\n", type); 361 plumbfree(m); 362 } 363 threadexits(nil); 364 } 365 366 void 367 plumbshowthread(void *v) 368 { 369 Plumbmsg *m; 370 371 USED(v); 372 threadsetname("plumbshowthread"); 373 while((m = recvp(cplumbshow)) != nil){ 374 showmesg(m->data, plumblookup(m->attr, "digest")); 375 plumbfree(m); 376 } 377 threadexits(nil); 378 } 379 380 void 381 plumbsendthread(void *v) 382 { 383 Plumbmsg *m; 384 385 USED(v); 386 threadsetname("plumbsendthread"); 387 while((m = recvp(cplumbsend)) != nil){ 388 mkreply(nil, "Mail", m->data, m->attr, nil); 389 plumbfree(m); 390 } 391 threadexits(nil); 392 } 393 394 int 395 mboxcommand(Window *w, char *s) 396 { 397 char *args[10], **targs, *save; 398 Window *sbox; 399 Message *m, *next; 400 int ok, nargs, i, j; 401 CFid *searchfd; 402 char buf[128], *res; 403 404 nargs = tokenize(s, args, nelem(args)); 405 if(nargs == 0) 406 return 0; 407 if(strcmp(args[0], "Mail") == 0){ 408 if(nargs == 1) 409 mkreply(nil, "Mail", "", nil, nil); 410 else 411 mkreply(nil, "Mail", args[1], nil, nil); 412 return 1; 413 } 414 if(strcmp(s, "Del") == 0){ 415 if(mbox.dirty){ 416 mbox.dirty = 0; 417 fprint(2, "mail: mailbox not written\n"); 418 return 1; 419 } 420 if(w != mbox.w){ 421 windel(w, 1); 422 return 1; 423 } 424 ok = 1; 425 for(m=mbox.head; m!=nil; m=next){ 426 next = m->next; 427 if(m->w){ 428 if(windel(m->w, 0)) 429 m->w = nil; 430 else 431 ok = 0; 432 } 433 } 434 for(m=replies.head; m!=nil; m=next){ 435 next = m->next; 436 if(m->w){ 437 if(windel(m->w, 0)) 438 m->w = nil; 439 else 440 ok = 0; 441 } 442 } 443 if(ok){ 444 windel(w, 1); 445 removeupasfs(); 446 threadexitsall(nil); 447 } 448 return 1; 449 } 450 if(strcmp(s, "Put") == 0){ 451 rewritembox(wbox, &mbox); 452 return 1; 453 } 454 if(strcmp(s, "Get") == 0){ 455 fswrite(mbox.ctlfd, "refresh", 7); 456 return 1; 457 } 458 if(strcmp(s, "Delmesg") == 0){ 459 save = nil; 460 if(nargs > 1) 461 save = args[1]; 462 s = winselection(w); 463 if(s == nil) 464 return 1; 465 nargs = 1; 466 for(i=0; s[i]; i++) 467 if(s[i] == '\n') 468 nargs++; 469 targs = emalloc(nargs*sizeof(char*)); /* could be too many for a local array */ 470 nargs = getfields(s, targs, nargs, 1, "\n"); 471 for(i=0; i<nargs; i++){ 472 if(!isdigit(targs[i][0])) 473 continue; 474 j = atoi(targs[i]); /* easy way to parse the number! */ 475 if(j == 0) 476 continue; 477 snprint(buf, sizeof buf, "%s%d", mbox.name, j); 478 delmesg(buf, nil, 1, save); 479 } 480 free(s); 481 free(targs); 482 return 1; 483 } 484 if(strcmp(s, "Search") == 0){ 485 if(nargs <= 1) 486 return 1; 487 s = estrstrdup(mboxname, "/search"); 488 searchfd = fsopen(mailfs, s, ORDWR); 489 if(searchfd == nil) 490 return 1; 491 save = estrdup(args[1]); 492 for(i=2; i<nargs; i++) 493 save = eappend(save, " ", args[i]); 494 fswrite(searchfd, save, strlen(save)); 495 fsseek(searchfd, 0, 0); 496 j = fsread(searchfd, buf, sizeof buf - 1); 497 if(j == 0){ 498 fprint(2, "[%s] search %s: no results found\n", mboxname, save); 499 fsclose(searchfd); 500 free(save); 501 return 1; 502 } 503 free(save); 504 buf[j] = '\0'; 505 res = estrdup(buf); 506 j = fsread(searchfd, buf, sizeof buf - 1); 507 for(; j != 0; j = fsread(searchfd, buf, sizeof buf - 1), buf[j] = '\0') 508 res = eappend(res, "", buf); 509 fsclose(searchfd); 510 511 sbox = newwindow(); 512 winname(sbox, s); 513 free(s); 514 threadcreate(mainctl, sbox, STACK); 515 winopenbody(sbox, OWRITE); 516 517 /* show results in reverse order */ 518 m = mbox.tail; 519 save = nil; 520 for(s=strrchr(res, ' '); s!=nil || save!=res; s=strrchr(res, ' ')){ 521 if(s != nil){ 522 save = s+1; 523 *s = '\0'; 524 } 525 else save = res; 526 save = estrstrdup(save, "/"); 527 for(; m && strcmp(save, m->name) != 0; m=m->prev); 528 free(save); 529 if(m == nil) 530 break; 531 fsprint(sbox->body, "%s%s\n", m->name, info(m, 0, 0)); 532 m = m->prev; 533 } 534 free(res); 535 winclean(sbox); 536 winclosebody(sbox); 537 return 1; 538 } 539 return 0; 540 } 541 542 void 543 mainctl(void *v) 544 { 545 Window *w; 546 Event *e, *e2, *eq, *ea; 547 int na, nopen; 548 char *s, *t, *buf; 549 550 w = v; 551 winincref(w); 552 proccreate(wineventproc, w, STACK); 553 554 for(;;){ 555 e = recvp(w->cevent); 556 switch(e->c1){ 557 default: 558 Unknown: 559 print("unknown message %c%c\n", e->c1, e->c2); 560 break; 561 562 case 'E': /* write to body; can't affect us */ 563 break; 564 565 case 'F': /* generated by our actions; ignore */ 566 break; 567 568 case 'K': /* type away; we don't care */ 569 break; 570 571 case 'M': 572 switch(e->c2){ 573 case 'x': 574 case 'X': 575 ea = nil; 576 e2 = nil; 577 if(e->flag & 2) 578 e2 = recvp(w->cevent); 579 if(e->flag & 8){ 580 ea = recvp(w->cevent); 581 na = ea->nb; 582 recvp(w->cevent); 583 }else 584 na = 0; 585 s = e->b; 586 /* if it's a known command, do it */ 587 if((e->flag&2) && e->nb==0) 588 s = e2->b; 589 if(na){ 590 t = emalloc(strlen(s)+1+na+1); 591 sprint(t, "%s %s", s, ea->b); 592 s = t; 593 } 594 /* if it's a long message, it can't be for us anyway */ 595 if(!mboxcommand(w, s)) /* send it back */ 596 winwriteevent(w, e); 597 if(na) 598 free(s); 599 break; 600 601 case 'l': 602 case 'L': 603 buf = nil; 604 eq = e; 605 if(e->flag & 2){ 606 e2 = recvp(w->cevent); 607 eq = e2; 608 } 609 s = eq->b; 610 if(eq->q1>eq->q0 && eq->nb==0){ 611 buf = emalloc((eq->q1-eq->q0)*UTFmax+1); 612 winread(w, eq->q0, eq->q1, buf); 613 s = buf; 614 } 615 nopen = 0; 616 do{ 617 /* skip 'deleted' string if present' */ 618 if(strncmp(s, deleted, strlen(deleted)) == 0) 619 s += strlen(deleted); 620 /* skip mail box name if present */ 621 if(strncmp(s, mbox.name, strlen(mbox.name)) == 0) 622 s += strlen(mbox.name); 623 nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil); 624 while(*s!='\0' && *s++!='\n') 625 ; 626 }while(*s); 627 if(nopen == 0) /* send it back */ 628 winwriteevent(w, e); 629 free(buf); 630 break; 631 632 case 'I': /* modify away; we don't care */ 633 case 'D': 634 case 'd': 635 case 'i': 636 break; 637 638 default: 639 goto Unknown; 640 } 641 } 642 } 643 }