| #include <u.h> |
| #include <libc.h> |
| #include <bio.h> |
| #include <thread.h> |
| #include <9pclient.h> |
| #include <plumb.h> |
| #include <ctype.h> |
| #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; |
| } |
| } |
| } |
| } |
| |