#include #include #include #include #include #include #include #include "dat.h" int debug; char *maildir = "/mail/fs/"; /* mountpoint of mail file system */ char *mailtermdir = "/mnt/term/mail/fs/"; /* alternate mountpoint */ char *mboxname = "mbox"; /* mailboxdir/mboxname is mail spool file */ char *mailboxdir = nil; /* nil == /mail/box/$user */ char *fsname; /* filesystem for mailboxdir/mboxname is at maildir/fsname */ char *user; char *outgoing; Window *wbox; Message mbox; Message replies; char *home; int plumbsendfd; int plumbseemailfd; int plumbshowmailfd; int plumbsendmailfd; Channel *cplumb; Channel *cplumbshow; Channel *cplumbsend; int wctlfd; void mainctl(void*); void plumbproc(void*); void plumbshowproc(void*); void plumbsendproc(void*); void plumbthread(void); void plumbshowthread(void*); void plumbsendthread(void*); int shortmenu; void omerogone(void) { threadexitsall(nil); } void usage(void) { fprint(2, "usage:\tomail [-dsS] [-m maildir] [mailboxname]\n"); fprint(2, " or:\tomail -n [-o outgoing] [addr [subject]]\n"); threadexitsall("usage"); } void removeupasfs(void) { char buf[256]; if(strcmp(mboxname, "mbox") == 0) return; snprint(buf, sizeof buf, "close %s", mboxname); write(mbox.ctlfd, buf, strlen(buf)); } int ismaildir(char *s) { char buf[256]; Dir *d; int ret; snprint(buf, sizeof buf, "%s%s", maildir, s); d = dirstat(buf); if(d == nil) return 0; ret = d->qid.type & QTDIR; free(d); return ret; } void threadmain(int argc, char *argv[]) { char *s, *name; char err[ERRMAX]; int i, newdir; int sendonly; char* subj; doquote = needsrcquote; quotefmtinstall(); sendonly = 0; shortmenu = 0; ARGBEGIN{ case 'n': sendonly = 1; break; case 'd': debug++; break; case 's': shortmenu = 1; break; case 'S': shortmenu = 2; break; case 'o': outgoing = EARGF(usage()); break; case 'm': smprint(maildir, "%s/", EARGF(usage())); break; default: usage(); }ARGEND if (sendonly){ mbox.name = "/mail/"; outgoing = smprint("/mail/box/%s/outgoing", getuser()); if (argc > 1) subj = argv[1]; else subj = nil; mkreply(nil, subj, "Mail", (argc ? argv[0] : ""), nil, nil); threadexits(nil); } /* open these early so we won't miss notification of new mail messages while we read mbox */ plumbsendfd = plumbopen("send", OWRITE|OCEXEC); plumbseemailfd = plumbopen("seemail", OREAD|OCEXEC); plumbshowmailfd = plumbopen("showmail", OREAD|OCEXEC); name = "mbox"; /* bind the terminal /mail/fs directory over the local one */ if(access(maildir, 0)<0 && access(mailtermdir, 0)==0) bind(mailtermdir, maildir, MAFTER); newdir = 1; if(argc > 0){ i = strlen(argv[0]); if(argc>2 || i==0) usage(); /* see if the name is that of an existing /mail/fs directory */ if(argc==1 && strchr(argv[0], '/')==0 && ismaildir(argv[0])){ name = argv[0]; mboxname = eappend(estrdup(maildir), "", name); newdir = 0; }else{ if(argv[0][i-1] == '/') argv[0][i-1] = '\0'; s = strrchr(argv[0], '/'); if(s == nil) mboxname = estrdup(argv[0]); else{ *s++ = '\0'; if(*s == '\0') usage(); mailboxdir = argv[0]; mboxname = estrdup(s); } if(argc > 1) name = argv[1]; else name = mboxname; } } user = getenv("user"); if(user == nil) user = "none"; if(mailboxdir == nil) mailboxdir = estrstrdup("/mail/box/", user); if(outgoing == nil) outgoing = estrstrdup(mailboxdir, "/outgoing"); s = estrstrdup(maildir, "ctl"); mbox.ctlfd = open(s, ORDWR|OCEXEC); if(mbox.ctlfd < 0) error("Mail: can't open %s: %r\n", s); fsname = estrdup(name); if(newdir && argc > 0){ s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1); for(i=0; i<10; i++){ sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname); if(write(mbox.ctlfd, s, strlen(s)) >= 0) break; err[0] = '\0'; errstr(err, sizeof err); if(strstr(err, "mbox name in use") == nil) error("Mail: can't create directory %s for mail: %s\n", name, err); free(fsname); fsname = emalloc(strlen(name)+10); sprint(fsname, "%s-%d", name, i); } if(i == 10) error("Mail: can't open %s/%s: %r", mailboxdir, mboxname); free(s); } s = estrstrdup(fsname, "/"); mbox.name = estrstrdup(maildir, s); mbox.level= 0; readmbox(&mbox, maildir, s); home = getenv("home"); if(home == nil) home = "/"; wbox = newwindow(mbox.name); wintagwrite(wbox, "Put Mail rm ", 3+1+4+1+7+1); threadcreate(mainctl, wbox, STACK); mbox.w = wbox; mesgmenu(wbox, &mbox); winclean(wbox); cplumb = chancreate(sizeof(Plumbmsg*), 0); cplumbshow = chancreate(sizeof(Plumbmsg*), 0); if(strcmp(name, "mbox") == 0){ /* * Avoid creating multiple windows to send mail by only accepting * sendmail plumb messages if we're reading the main mailbox. */ plumbsendmailfd = plumbopen("sendmail", OREAD|OCEXEC); cplumbsend = chancreate(sizeof(Plumbmsg*), 0); proccreate(plumbsendproc, nil, STACK); threadcreate(plumbsendthread, nil, STACK); } /* start plumb reader as separate proc ... */ proccreate(plumbproc, nil, STACK); proccreate(plumbshowproc, nil, STACK); threadcreate(plumbshowthread, nil, STACK); /* ... and use this thread to read the messages */ plumbthread(); } void plumbproc(void*) { Plumbmsg *m; threadsetname("plumbproc"); for(;;){ m = plumbrecv(plumbseemailfd); sendp(cplumb, m); if(m == nil) threadexits(nil); } } void plumbshowproc(void*) { Plumbmsg *m; threadsetname("plumbshowproc"); for(;;){ m = plumbrecv(plumbshowmailfd); sendp(cplumbshow, m); if(m == nil) threadexits(nil); } } void plumbsendproc(void*) { Plumbmsg *m; threadsetname("plumbsendproc"); for(;;){ m = plumbrecv(plumbsendmailfd); sendp(cplumbsend, m); if(m == nil) threadexits(nil); } } void newmesg(char *name, char *digest) { Dir *d; if(strncmp(name, mbox.name, strlen(mbox.name)) != 0) return; /* message is about another mailbox */ if(mesglookupfile(&mbox, name, digest) != nil) return; d = dirstat(name); if(d == nil) return; if(mesgadd(&mbox, mbox.name, d, digest)) mesgmenu(wbox, &mbox); free(d); } void showmesg(char *name, char *digest) { char *n; if(strncmp(name, mbox.name, strlen(mbox.name)) != 0) return; /* message is about another mailbox */ n = estrdup(name+strlen(mbox.name)); if(n[strlen(n)-1] != '/') n = egrow(n, "/", nil); mesgopen(&mbox, mbox.name, name+strlen(mbox.name), nil, digest); free(n); } void delmesg(char *name, char *digest, int dodel) { Message *m; m = mesglookupfile(&mbox, name, digest); if(m != nil){ mesgmenumarkdel(wbox, &mbox, m, 0); if(dodel) m->writebackdel = 1; } } void plumbthread(void) { Plumbmsg *m; Plumbattr *a; char *type, *digest; threadsetname("plumbthread"); while((m = recvp(cplumb)) != nil){ a = m->attr; digest = plumblookup(a, "digest"); type = plumblookup(a, "mailtype"); if(type == nil) fprint(2, "Mail: plumb message with no mailtype attribute\n"); else if(strcmp(type, "new") == 0) newmesg(m->data, digest); else if(strcmp(type, "delete") == 0) delmesg(m->data, digest, 0); else fprint(2, "Mail: unknown plumb attribute %s\n", type); plumbfree(m); } threadexits(nil); } void plumbshowthread(void*) { Plumbmsg *m; threadsetname("plumbshowthread"); while((m = recvp(cplumbshow)) != nil){ showmesg(m->data, plumblookup(m->attr, "digest")); plumbfree(m); } threadexits(nil); } void plumbsendthread(void*) { Plumbmsg *m; threadsetname("plumbsendthread"); while((m = recvp(cplumbsend)) != nil){ mkreply(nil,nil, "Mail", m->data, m->attr, nil); plumbfree(m); } threadexits(nil); } int mboxcommand(Window *w, char *s) { char *args[10]; Message *m, *next; int ok, nargs, i; char buf[128]; s = strdup(s); nargs = tokenize(s, args, nelem(args)); if(nargs == 0){ free(s); return 0; } if(strcmp(args[0], "Mail") == 0){ if(nargs == 1) mkreply(nil, nil, "Mail", "", nil, nil); else mkreply(nil, nil, "Mail", args[1], nil, nil); free(s); return 1; } if(strcmp(s, "Done") == 0){ if(mbox.dirty){ mbox.dirty = 0; fprint(2, "mail: mailbox not written\n"); free(s); return 1; } ok = 1; for(m=mbox.head; m!=nil; m=next){ next = m->next; if(m->w){ if(windel(m->w)) m->w = nil; else ok = 0; } } for(m=replies.head; m!=nil; m=next){ next = m->next; if(m->w){ if(windel(m->w)) m->w = nil; else ok = 0; } } if(ok){ windel(w); removeupasfs(); threadexitsall(nil); } free(s); return 1; } if(strcmp(s, "Put") == 0){ rewritembox(wbox, &mbox); mesgmenu(wbox, &mbox); free(s); return 1; } if(strcmp(s, "Rm") == 0){ if(nargs > 1){ for(i=1; ig->evc; while(recv(c, &e) != -1){ dprint(2, "event %s %s\n", e.ev, e.arg); if (!strcmp(e.ev, "exec")){ if(!mboxcommand(w, e.arg+12)) /* send it back */ plumbexec(mbox.name, e.arg); } else if (!strcmp(e.ev, "look")){ if (!mesgopen(&mbox, mbox.name, e.arg+12, nil, nil)) plumblook(mbox.name, e.arg); } else if (!strcmp(e.ev, "exit")){ clearoev(&e); break; } clearoev(&e); } chanfree(c); threadexits(nil); }