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