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