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