#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; i < nargs; i++) { if (!isdigit(targs[i][0])) continue; j = atoi(targs[i]); /* easy way to parse the number! */ if (j == 0) continue; snprint(buf, sizeof buf, "%s%d", mbox.name, j); delmesg(buf, nil, 1, save); } free(s); free(targs); return 1; } if (strcmp(s, "Search") == 0) { if (nargs <= 1) return 1; s = estrstrdup(mboxname, "/search"); searchfd = fsopen(mailfs, s, ORDWR); if (searchfd == nil) return 1; save = estrdup(args[1]); for (i = 2; i < nargs; i++) save = eappend(save, " ", args[i]); fswrite(searchfd, save, strlen(save)); fsseek(searchfd, 0, 0); j = fsread(searchfd, buf, sizeof buf - 1); if (j == 0) { fprint(2, "[%s] search %s: no results found\n", mboxname, save); fsclose(searchfd); free(save); return 1; } free(save); buf[j] = '\0'; res = estrdup(buf); j = fsread(searchfd, buf, sizeof buf - 1); for (; j != 0; j = fsread(searchfd, buf, sizeof buf - 1), buf[j] = '\0') res = eappend(res, "", buf); fsclose(searchfd); sbox = newwindow(); winname(sbox, s); free(s); threadcreate(mainctl, sbox, STACK); winopenbody(sbox, OWRITE); /* show results in reverse order */ m = mbox.tail; save = nil; for (s = strrchr(res, ' '); s != nil || save != res; s = strrchr(res, ' ')) { if (s != nil) { save = s + 1; *s = '\0'; } else save = res; save = estrstrdup(save, "/"); for (; m && strcmp(save, m->name) != 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; } } } }