#include #include #include #include #include <9pclient.h> #include #include #include "dat.h" char *maildir = "Mail/"; /* mountpoint of mail file system */ 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; char *srvname; Window *wbox; Message mbox; Message replies; char *home; CFid *plumbsendfd; CFid *plumbseemailfd; CFid *plumbshowmailfd; CFid *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; CFsys *mailfs; CFsys *acmefs; void usage(void) { fprint(2, "usage: Mail [-sS] [-n srvname] [-o outgoing] [mailboxname [directoryname]]\n"); threadexitsall("usage"); } void removeupasfs(void) { char buf[256]; if(strcmp(mboxname, "mbox") == 0) return; snprint(buf, sizeof buf, "close %s", mboxname); fswrite(mbox.ctlfd, buf, strlen(buf)); } int ismaildir(char *s) { Dir *d; int ret; d = fsdirstat(mailfs, s); 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], *cmd; int i, newdir; Fmt fmt; doquote = needsrcquote; quotefmtinstall(); /* open these early so we won't miss notification of new mail messages while we read mbox */ if((plumbsendfd = plumbopenfid("send", OWRITE|OCEXEC)) == nil) fprint(2, "warning: open plumb/send: %r\n"); if((plumbseemailfd = plumbopenfid("seemail", OREAD|OCEXEC)) == nil) fprint(2, "warning: open plumb/seemail: %r\n"); if((plumbshowmailfd = plumbopenfid("showmail", OREAD|OCEXEC)) == nil) fprint(2, "warning: open plumb/showmail: %r\n"); shortmenu = 0; srvname = "mail"; ARGBEGIN{ case 's': shortmenu = 1; break; case 'S': shortmenu = 2; break; case 'o': outgoing = EARGF(usage()); break; case 'm': smprint(maildir, "%s/", EARGF(usage())); break; case 'n': srvname = EARGF(usage()); break; default: usage(); }ARGEND acmefs = nsmount("acme",nil); if(acmefs == nil) error("cannot mount acme: %r"); mailfs = nsmount(srvname, nil); if(mailfs == nil) error("cannot mount %s: %r", srvname); name = "mbox"; 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 && argv[0][0] != '/' && ismaildir(argv[0])){ name = argv[0]; mboxname = estrdup(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"; home = getenv("home"); if(home == nil) home = getenv("HOME"); if(home == nil) error("can't find $home"); if(mailboxdir == nil) mailboxdir = estrstrdup(home, "/mail"); if(outgoing == nil) outgoing = estrstrdup(mailboxdir, "/outgoing"); mbox.ctlfd = fsopen(mailfs, estrstrdup(mboxname, "/ctl"), OWRITE); if(mbox.ctlfd == nil) error("can't open %s: %r", estrstrdup(mboxname, "/ctl")); 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(fswrite(mbox.ctlfd, s, strlen(s)) >= 0) break; err[0] = '\0'; errstr(err, sizeof err); if(strstr(err, "mbox name in use") == nil) error("can't create directory %s for mail: %s", name, err); free(fsname); fsname = emalloc(strlen(name)+10); sprint(fsname, "%s-%d", name, i); } if(i == 10) error("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(); winname(wbox, mbox.name); wintagwrite(wbox, "Put Mail Delmesg ", 3+1+4+1+7+1); threadcreate(mainctl, wbox, STACK); fmtstrinit(&fmt); fmtprint(&fmt, "Mail"); if(shortmenu) fmtprint(&fmt, " -%c", "sS"[shortmenu-1]); if(outgoing) fmtprint(&fmt, " -o %s", outgoing); fmtprint(&fmt, " %s", name); cmd = fmtstrflush(&fmt); if(cmd == nil) sysfatal("out of memory"); winsetdump(wbox, "/acme/mail", cmd); mbox.w = wbox; mesgmenu(wbox, &mbox); winclean(wbox); /* wctlfd = open("/dev/wctl", OWRITE|OCEXEC); /* for acme window */ wctlfd = -1; 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 = plumbopenfid("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); fswrite(mbox.ctlfd, "refresh", 7); /* ... and use this thread to read the messages */ plumbthread(); } void plumbproc(void* v) { Plumbmsg *m; threadsetname("plumbproc"); for(;;){ m = plumbrecvfid(plumbseemailfd); sendp(cplumb, m); if(m == nil) threadexits(nil); } } void plumbshowproc(void* v) { Plumbmsg *m; threadsetname("plumbshowproc"); for(;;){ m = plumbrecvfid(plumbshowmailfd); sendp(cplumbshow, m); if(m == nil) threadexits(nil); } } void plumbsendproc(void* v) { Plumbmsg *m; threadsetname("plumbsendproc"); for(;;){ m = plumbrecvfid(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; if(strncmp(name, "Mail/", 5) == 0) name += 5; d = fsdirstat(mailfs, name); if(d == nil) return; if(mesgadd(&mbox, mbox.name, d, digest)) mesgmenunew(wbox, &mbox); free(d); } void showmesg(char *name, char *digest) { char *n; char *mb; mb = mbox.name; if(strncmp(name, mb, strlen(mb)) != 0) return; /* message is about another mailbox */ n = estrdup(name+strlen(mb)); if(n[strlen(n)-1] != '/') n = egrow(n, "/", nil); mesgopen(&mbox, mbox.name, name+strlen(mb), nil, 1, digest); free(n); } void delmesg(char *name, char *digest, int dodel, char *save) { Message *m; m = mesglookupfile(&mbox, name, digest); if(m != nil){ if(save) mesgcommand(m, estrstrdup("Save ", save)); if(dodel) mesgmenumarkdel(wbox, &mbox, m, 1); else{ /* notification came from plumber - message is gone */ mesgmenudel(wbox, &mbox, m); if(!m->opened) mesgdel(&mbox, m); } } } 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, nil); else fprint(2, "Mail: unknown plumb attribute %s\n", type); plumbfree(m); } threadexits(nil); } void plumbshowthread(void *v) { Plumbmsg *m; USED(v); threadsetname("plumbshowthread"); while((m = recvp(cplumbshow)) != nil){ showmesg(m->data, plumblookup(m->attr, "digest")); plumbfree(m); } threadexits(nil); } void plumbsendthread(void *v) { Plumbmsg *m; USED(v); threadsetname("plumbsendthread"); while((m = recvp(cplumbsend)) != nil){ mkreply(nil, "Mail", m->data, m->attr, nil); plumbfree(m); } threadexits(nil); } int mboxcommand(Window *w, char *s) { char *args[10], **targs, *save; Window *sbox; Message *m, *next; int ok, nargs, i, j; CFid *searchfd; char buf[128], *res; nargs = tokenize(s, args, nelem(args)); if(nargs == 0) return 0; if(strcmp(args[0], "Mail") == 0){ if(nargs == 1) mkreply(nil, "Mail", "", nil, nil); else mkreply(nil, "Mail", args[1], nil, nil); return 1; } if(strcmp(s, "Del") == 0){ if(mbox.dirty){ mbox.dirty = 0; fprint(2, "mail: mailbox not written\n"); return 1; } if(w != mbox.w){ windel(w, 1); return 1; } ok = 1; for(m=mbox.head; m!=nil; m=next){ next = m->next; if(m->w){ if(windel(m->w, 0)) m->w = nil; else ok = 0; } } for(m=replies.head; m!=nil; m=next){ next = m->next; if(m->w){ if(windel(m->w, 0)) m->w = nil; else ok = 0; } } if(ok){ windel(w, 1); removeupasfs(); threadexitsall(nil); } return 1; } if(strcmp(s, "Put") == 0){ rewritembox(wbox, &mbox); return 1; } if(strcmp(s, "Get") == 0){ fswrite(mbox.ctlfd, "refresh", 7); return 1; } if(strcmp(s, "Delmesg") == 0){ save = nil; if(nargs > 1) save = args[1]; s = winselection(w); if(s == nil) return 1; nargs = 1; for(i=0; s[i]; i++) if(s[i] == '\n') nargs++; targs = emalloc(nargs*sizeof(char*)); /* could be too many for a local array */ nargs = getfields(s, targs, nargs, 1, "\n"); for(i=0; iname) != 0; m=m->prev); free(save); if(m == nil) break; fsprint(sbox->body, "%s%s\n", m->name, info(m, 0, 0)); m = m->prev; } free(res); winclean(sbox); winclosebody(sbox); return 1; } return 0; } void mainctl(void *v) { Window *w; Event *e, *e2, *eq, *ea; int na, nopen; char *s, *t, *buf; w = v; winincref(w); proccreate(wineventproc, w, STACK); for(;;){ e = recvp(w->cevent); switch(e->c1){ default: Unknown: print("unknown message %c%c\n", e->c1, e->c2); break; case 'E': /* write to body; can't affect us */ break; case 'F': /* generated by our actions; ignore */ break; case 'K': /* type away; we don't care */ break; case 'M': switch(e->c2){ case 'x': case 'X': ea = nil; e2 = nil; if(e->flag & 2) e2 = recvp(w->cevent); if(e->flag & 8){ ea = recvp(w->cevent); na = ea->nb; recvp(w->cevent); }else na = 0; s = e->b; /* if it's a known command, do it */ if((e->flag&2) && e->nb==0) s = e2->b; if(na){ t = emalloc(strlen(s)+1+na+1); sprint(t, "%s %s", s, ea->b); s = t; } /* if it's a long message, it can't be for us anyway */ if(!mboxcommand(w, s)) /* send it back */ winwriteevent(w, e); if(na) free(s); break; case 'l': case 'L': buf = nil; eq = e; if(e->flag & 2){ e2 = recvp(w->cevent); eq = e2; } s = eq->b; if(eq->q1>eq->q0 && eq->nb==0){ buf = emalloc((eq->q1-eq->q0)*UTFmax+1); winread(w, eq->q0, eq->q1, buf); s = buf; } nopen = 0; do{ /* skip 'deleted' string if present' */ if(strncmp(s, deleted, strlen(deleted)) == 0) s += strlen(deleted); /* skip mail box name if present */ if(strncmp(s, mbox.name, strlen(mbox.name)) == 0) s += strlen(mbox.name); nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil); while(*s!='\0' && *s++!='\n') ; }while(*s); if(nopen == 0) /* send it back */ winwriteevent(w, e); free(buf); break; case 'I': /* modify away; we don't care */ case 'D': case 'd': case 'i': break; default: goto Unknown; } } } }