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