acme9k/mail/mail.c

622 lines
15 KiB
C

#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;
}
}
}
}