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