1257 lines
22 KiB
C
1257 lines
22 KiB
C
/*
|
|
* Mail file system.
|
|
*
|
|
* Serve the bulk of requests out of memory, so they can
|
|
* be in the main loop (will never see their flushes).
|
|
* Some requests do block and they get handled in
|
|
* separate threads. They're all okay to give up on
|
|
* early, though, so we just respond saying interrupted
|
|
* and then when they finish, silently discard the request.
|
|
|
|
TO DO:
|
|
|
|
decode subject, etc.
|
|
decode body
|
|
|
|
digest
|
|
disposition
|
|
filename
|
|
|
|
ctl messages
|
|
|
|
fetch mail on demand
|
|
|
|
*/
|
|
|
|
#include "a.h"
|
|
|
|
enum
|
|
{
|
|
/* directories */
|
|
Qroot,
|
|
Qbox,
|
|
Qmsg,
|
|
|
|
/* control files */
|
|
Qctl,
|
|
Qboxctl,
|
|
Qsearch,
|
|
|
|
/* message header - same order as struct Hdr */
|
|
Qdate,
|
|
Qsubject,
|
|
Qfrom,
|
|
Qsender,
|
|
Qreplyto,
|
|
Qto,
|
|
Qcc,
|
|
Qbcc,
|
|
Qinreplyto,
|
|
Qmessageid,
|
|
|
|
/* part data - same order as stuct Part */
|
|
Qtype,
|
|
Qidstr,
|
|
Qdesc,
|
|
Qencoding, /* only here temporarily! */
|
|
Qcharset,
|
|
Qraw,
|
|
Qrawheader,
|
|
Qrawbody,
|
|
Qmimeheader,
|
|
|
|
/* part numbers - same order as struct Part */
|
|
Qsize,
|
|
Qlines,
|
|
|
|
/* other message files */
|
|
Qbody,
|
|
Qheader,
|
|
Qdigest,
|
|
Qdisposition,
|
|
Qfilename,
|
|
Qflags,
|
|
Qinfo,
|
|
Qrawunix,
|
|
Qunixdate,
|
|
Qunixheader,
|
|
|
|
Qfile0 = Qbody,
|
|
Qnfile = Qunixheader+1-Qfile0
|
|
};
|
|
|
|
static char Egreg[] = "gone postal";
|
|
static char Enobox[] = "no such mailbox";
|
|
static char Enomsg[] = "no such message";
|
|
static char Eboxgone[] = "mailbox not available";
|
|
static char Emsggone[] = "message not available";
|
|
static char Eperm[] = "permission denied";
|
|
static char Ebadctl[] = "bad control message";
|
|
|
|
Channel *fsreqchan;
|
|
Srv fs;
|
|
Qid rootqid;
|
|
ulong t0;
|
|
|
|
void
|
|
responderror(Req *r)
|
|
{
|
|
char e[ERRMAX];
|
|
|
|
rerrstr(e, sizeof e);
|
|
respond(r, e);
|
|
}
|
|
|
|
int
|
|
qtype(Qid q)
|
|
{
|
|
return q.path&0x3F;
|
|
}
|
|
|
|
int
|
|
qboxid(Qid q)
|
|
{
|
|
return (q.path>>40)&0xFFFF;
|
|
}
|
|
|
|
int
|
|
qmsgid(Qid q)
|
|
{
|
|
return ((q.path>>32)&0xFF000000) | ((q.path>>16)&0xFFFFFF);
|
|
}
|
|
|
|
int
|
|
qpartid(Qid q)
|
|
{
|
|
return ((q.path>>6)&0x3FF);
|
|
}
|
|
|
|
Qid
|
|
qid(int ctl, Box *box, Msg *msg, Part *part)
|
|
{
|
|
Qid q;
|
|
|
|
q.type = 0;
|
|
if(ctl == Qroot || ctl == Qbox || ctl == Qmsg)
|
|
q.type = QTDIR;
|
|
q.path = (vlong)((msg ? msg->id : 0)&0xFF000000)<<32;
|
|
q.path |= (vlong)((msg ? msg->id : 0)&0xFFFFFF)<<16;
|
|
q.path |= (vlong)((box ? box->id : 0)&0xFFFF)<<40;
|
|
q.path |= ((part ? part->ix : 0)&0x3FF)<<6;
|
|
q.path |= ctl&0x3F;
|
|
q.vers = box ? box->validity : 0;
|
|
return q;
|
|
}
|
|
|
|
int
|
|
parseqid(Qid q, Box **box, Msg **msg, Part **part)
|
|
{
|
|
*msg = nil;
|
|
*part = nil;
|
|
|
|
*box = boxbyid(qboxid(q));
|
|
if(*box){
|
|
*msg = msgbyid(*box, qmsgid(q));
|
|
}
|
|
if(*msg)
|
|
*part = partbyid(*msg, qpartid(q));
|
|
return qtype(q);
|
|
}
|
|
|
|
static struct {
|
|
int type;
|
|
char *name;
|
|
} typenames[] = {
|
|
Qbody, "body",
|
|
Qbcc, "bcc",
|
|
Qcc, "cc",
|
|
Qdate, "date",
|
|
Qfilename, "filename",
|
|
Qflags, "flags",
|
|
Qfrom, "from",
|
|
Qheader, "header",
|
|
Qinfo, "info",
|
|
Qinreplyto, "inreplyto",
|
|
Qlines, "lines",
|
|
Qmimeheader, "mimeheader",
|
|
Qmessageid, "messageid",
|
|
Qraw, "raw",
|
|
Qrawunix, "rawunix",
|
|
Qrawbody, "rawbody",
|
|
Qrawheader, "rawheader",
|
|
Qreplyto, "replyto",
|
|
Qsender, "sender",
|
|
Qsubject, "subject",
|
|
Qto, "to",
|
|
Qtype, "type",
|
|
Qunixdate, "unixdate",
|
|
Qunixheader, "unixheader",
|
|
Qidstr, "idstr",
|
|
Qdesc, "desc",
|
|
Qencoding, "encoding",
|
|
Qcharset, "charset"
|
|
};
|
|
|
|
char*
|
|
nameoftype(int t)
|
|
{
|
|
int i;
|
|
|
|
for(i=0; i<nelem(typenames); i++)
|
|
if(typenames[i].type == t)
|
|
return typenames[i].name;
|
|
return "???";
|
|
}
|
|
|
|
int
|
|
typeofname(char *name)
|
|
{
|
|
int i;
|
|
|
|
for(i=0; i<nelem(typenames); i++)
|
|
if(strcmp(typenames[i].name, name) == 0)
|
|
return typenames[i].type;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
fsattach(Req *r)
|
|
{
|
|
r->fid->qid = rootqid;
|
|
r->ofcall.qid = rootqid;
|
|
respond(r, nil);
|
|
}
|
|
|
|
static int
|
|
isnumber(char *s)
|
|
{
|
|
int n;
|
|
|
|
if(*s < '1' || *s > '9')
|
|
return 0;
|
|
n = strtol(s, &s, 10);
|
|
if(*s != 0)
|
|
return 0;
|
|
return n;
|
|
}
|
|
|
|
static char*
|
|
fswalk1(Fid *fid, char *name, void *arg)
|
|
{
|
|
int a, type;
|
|
Box *b, *box;
|
|
Msg *msg;
|
|
Part *p, *part;
|
|
|
|
USED(arg);
|
|
|
|
switch(type = parseqid(fid->qid, &box, &msg, &part)){
|
|
case Qroot:
|
|
if(strcmp(name, "..") == 0)
|
|
return nil;
|
|
if(strcmp(name, "ctl") == 0){
|
|
fid->qid = qid(Qctl, nil, nil, nil);
|
|
return nil;
|
|
}
|
|
if((box = boxbyname(name)) != nil){
|
|
fid->qid = qid(Qbox, box, nil, nil);
|
|
return nil;
|
|
}
|
|
break;
|
|
|
|
case Qbox:
|
|
/*
|
|
* Would be nice if .. could work even if the box is gone,
|
|
* but we don't know how deep the directory was.
|
|
*/
|
|
if(box == nil)
|
|
return Eboxgone;
|
|
if(strcmp(name, "..") == 0){
|
|
if((box = box->parent) == nil){
|
|
fid->qid = rootqid;
|
|
return nil;
|
|
}
|
|
fid->qid = qid(Qbox, box, nil, nil);
|
|
return nil;
|
|
}
|
|
if(strcmp(name, "ctl") == 0){
|
|
fid->qid = qid(Qboxctl, box, nil, nil);
|
|
return nil;
|
|
}
|
|
if(strcmp(name, "search") == 0){
|
|
fid->qid = qid(Qsearch, box, nil, nil);
|
|
return nil;
|
|
}
|
|
if((b = subbox(box, name)) != nil){
|
|
fid->qid = qid(Qbox, b, nil, nil);
|
|
return nil;
|
|
}
|
|
if((a = isnumber(name)) != 0){
|
|
if((msg = msgbyid(box, a)) == nil){
|
|
return Enomsg;
|
|
}
|
|
fid->qid = qid(Qmsg, box, msg, nil);
|
|
return nil;
|
|
}
|
|
break;
|
|
|
|
case Qmsg:
|
|
if(strcmp(name, "..") == 0){
|
|
if(part == msg->part[0]){
|
|
fid->qid = qid(Qbox, box, nil, nil);
|
|
return nil;
|
|
}
|
|
fid->qid = qid(Qmsg, box, msg, part->parent);
|
|
return nil;
|
|
}
|
|
if((type = typeofname(name)) > 0){
|
|
/* XXX - should check that type makes sense (see msggen) */
|
|
fid->qid = qid(type, box, msg, part);
|
|
return nil;
|
|
}
|
|
if((a = isnumber(name)) != 0){
|
|
if((p = subpart(part, a-1)) != nil){
|
|
fid->qid = qid(Qmsg, box, msg, p);
|
|
return nil;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return "not found";
|
|
}
|
|
|
|
static void
|
|
fswalk(Req *r)
|
|
{
|
|
walkandclone(r, fswalk1, nil, nil);
|
|
}
|
|
|
|
static struct {
|
|
int flag;
|
|
char *name;
|
|
} flagtab[] = {
|
|
FlagJunk, "junk",
|
|
FlagNonJunk, "notjunk",
|
|
FlagReplied, "replied",
|
|
FlagFlagged, "flagged",
|
|
/* FlagDeleted, "deleted", */
|
|
FlagDraft, "draft",
|
|
FlagSeen, "seen"
|
|
};
|
|
|
|
static void
|
|
addaddrs(Fmt *fmt, char *prefix, char *addrs)
|
|
{
|
|
char **f;
|
|
int i, nf, inquote;
|
|
char *p, *sep;
|
|
|
|
if(addrs == nil)
|
|
return;
|
|
addrs = estrdup(addrs);
|
|
nf = 0;
|
|
inquote = 0;
|
|
for(p=addrs; *p; p++){
|
|
if(*p == ' ' && !inquote)
|
|
nf++;
|
|
if(*p == '\'')
|
|
inquote = !inquote;
|
|
}
|
|
nf += 10;
|
|
f = emalloc(nf*sizeof f[0]);
|
|
nf = tokenize(addrs, f, nf);
|
|
fmtprint(fmt, "%s:", prefix);
|
|
sep = " ";
|
|
for(i=0; i+1<nf; i+=2){
|
|
if(f[i][0])
|
|
fmtprint(fmt, "%s%s <%s>", sep, f[i], f[i+1]);
|
|
else
|
|
fmtprint(fmt, "%s%s", sep, f[i+1]);
|
|
sep = ", ";
|
|
}
|
|
fmtprint(fmt, "\n");
|
|
free(addrs);
|
|
}
|
|
|
|
static void
|
|
mkbody(Part *p, Qid q)
|
|
{
|
|
char *t;
|
|
int len;
|
|
|
|
if(p->msg->part[0] == p)
|
|
t = p->rawbody;
|
|
else
|
|
t = p->raw;
|
|
if(t == nil)
|
|
return;
|
|
|
|
len = -1;
|
|
if(p->encoding && cistrcmp(p->encoding, "quoted-printable") == 0)
|
|
t = decode(QuotedPrintable, t, &len);
|
|
else if(p->encoding && cistrcmp(p->encoding, "base64") == 0)
|
|
t = decode(Base64, t, &len);
|
|
else
|
|
t = estrdup(t);
|
|
|
|
if(p->charset){
|
|
t = tcs(p->charset, t);
|
|
len = -1;
|
|
}
|
|
p->body = t;
|
|
if(len == -1)
|
|
p->nbody = strlen(t);
|
|
else
|
|
p->nbody = len;
|
|
}
|
|
|
|
static Qid ZQ;
|
|
|
|
static int
|
|
filedata(int type, Box *box, Msg *msg, Part *part, char **pp, int *len, int *freeme, int force, Qid q)
|
|
{
|
|
int i, inquote, n, t;
|
|
char *from, *s;
|
|
static char buf[256];
|
|
Fmt fmt;
|
|
|
|
*pp = nil;
|
|
*freeme = 0;
|
|
if(len)
|
|
*len = -1;
|
|
|
|
if(msg == nil || part == nil){
|
|
werrstr(Emsggone);
|
|
return -1;
|
|
}
|
|
switch(type){
|
|
case Qdate:
|
|
case Qsubject:
|
|
case Qfrom:
|
|
case Qsender:
|
|
case Qreplyto:
|
|
case Qto:
|
|
case Qcc:
|
|
case Qbcc:
|
|
case Qinreplyto:
|
|
case Qmessageid:
|
|
if(part->hdr == nil){
|
|
werrstr(Emsggone);
|
|
return -1;
|
|
}
|
|
*pp = ((char**)&part->hdr->date)[type-Qdate];
|
|
return 0;
|
|
|
|
case Qunixdate:
|
|
strcpy(buf, ctime(msg->date));
|
|
*pp = buf;
|
|
return 0;
|
|
|
|
case Qunixheader:
|
|
if(part->hdr == nil){
|
|
werrstr(Emsggone);
|
|
return -1;
|
|
}
|
|
from = part->hdr->from;
|
|
if(from == nil)
|
|
from = "???";
|
|
else{
|
|
inquote = 0;
|
|
for(; *from; from++){
|
|
if(*from == '\'')
|
|
inquote = !inquote;
|
|
if(!inquote && *from == ' '){
|
|
from++;
|
|
break;
|
|
}
|
|
}
|
|
if(*from == 0)
|
|
from = part->hdr->from;
|
|
}
|
|
n = snprint(buf, sizeof buf, "From %s %s", from, ctime(msg->date));
|
|
if(n+1 < sizeof buf){
|
|
*pp = buf;
|
|
return 0;
|
|
}
|
|
fmtstrinit(&fmt);
|
|
fmtprint(&fmt, "From %s %s", from, ctime(msg->date));
|
|
s = fmtstrflush(&fmt);
|
|
if(s){
|
|
*pp = s;
|
|
*freeme = 1;
|
|
}else
|
|
*pp = buf;
|
|
return 0;
|
|
|
|
case Qtype:
|
|
case Qidstr:
|
|
case Qdesc:
|
|
case Qencoding:
|
|
case Qcharset:
|
|
case Qraw:
|
|
case Qrawheader:
|
|
case Qrawbody:
|
|
case Qmimeheader:
|
|
*pp = ((char**)&part->type)[type-Qtype];
|
|
if(*pp == nil && force){
|
|
switch(type){
|
|
case Qraw:
|
|
imapfetchraw(imap, part);
|
|
break;
|
|
case Qrawheader:
|
|
imapfetchrawheader(imap, part);
|
|
break;
|
|
case Qrawbody:
|
|
imapfetchrawbody(imap, part);
|
|
break;
|
|
case Qmimeheader:
|
|
imapfetchrawmime(imap, part);
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
/*
|
|
* We ran fetchsomething, which might have changed
|
|
* the mailbox contents. Msg might even be gone.
|
|
*/
|
|
t = parseqid(q, &box, &msg, &part);
|
|
if(t != type || msg == nil || part == nil)
|
|
return 0;
|
|
*pp = ((char**)&part->type)[type-Qtype];
|
|
}
|
|
return 0;
|
|
|
|
case Qbody:
|
|
if(part->body){
|
|
*pp = part->body;
|
|
if(len)
|
|
*len = part->nbody;
|
|
return 0;
|
|
}
|
|
if(!force)
|
|
return 0;
|
|
if(part->rawbody == nil){
|
|
if(part->msg->part[0] == part)
|
|
imapfetchrawbody(imap, part);
|
|
else
|
|
imapfetchraw(imap, part);
|
|
t = parseqid(q, &box, &msg, &part);
|
|
if(t != type || msg == nil || part == nil)
|
|
return 0;
|
|
}
|
|
mkbody(part, q);
|
|
*pp = part->body;
|
|
if(len)
|
|
*len = part->nbody;
|
|
return 0;
|
|
|
|
case Qsize:
|
|
case Qlines:
|
|
n = ((uint*)&part->size)[type-Qsize];
|
|
snprint(buf, sizeof buf, "%d", n);
|
|
*pp = buf;
|
|
return 0;
|
|
|
|
case Qflags:
|
|
s = buf;
|
|
*s = 0;
|
|
for(i=0; i<nelem(flagtab); i++){
|
|
if(msg->flags&flagtab[i].flag){
|
|
if(s > buf)
|
|
*s++ = ' ';
|
|
strcpy(s, flagtab[i].name);
|
|
s += strlen(s);
|
|
}
|
|
}
|
|
*pp = buf;
|
|
return 0;
|
|
|
|
case Qinfo:
|
|
fmtstrinit(&fmt);
|
|
if(part == msg->part[0]){
|
|
if(msg->date)
|
|
fmtprint(&fmt, "unixdate %lud %s", msg->date, ctime(msg->date));
|
|
if(msg->flags){
|
|
filedata(Qflags, box, msg, part, pp, nil, freeme, 0, ZQ);
|
|
fmtprint(&fmt, "flags %s\n", buf);
|
|
}
|
|
}
|
|
if(part->hdr){
|
|
if(part->hdr->digest)
|
|
fmtprint(&fmt, "digest %s\n", part->hdr->digest);
|
|
if(part->hdr->from)
|
|
fmtprint(&fmt, "from %s\n", part->hdr->from);
|
|
if(part->hdr->to)
|
|
fmtprint(&fmt, "to %s\n", part->hdr->to);
|
|
if(part->hdr->cc)
|
|
fmtprint(&fmt, "cc %s\n", part->hdr->cc);
|
|
if(part->hdr->replyto)
|
|
fmtprint(&fmt, "replyto %s\n", part->hdr->replyto);
|
|
if(part->hdr->bcc)
|
|
fmtprint(&fmt, "bcc %s\n", part->hdr->bcc);
|
|
if(part->hdr->inreplyto)
|
|
fmtprint(&fmt, "inreplyto %s\n", part->hdr->inreplyto);
|
|
if(part->hdr->date)
|
|
fmtprint(&fmt, "date %s\n", part->hdr->date);
|
|
if(part->hdr->sender)
|
|
fmtprint(&fmt, "sender %s\n", part->hdr->sender);
|
|
if(part->hdr->messageid)
|
|
fmtprint(&fmt, "messageid %s\n", part->hdr->messageid);
|
|
if(part->hdr->subject)
|
|
fmtprint(&fmt, "subject %s\n", part->hdr->subject);
|
|
}
|
|
if(part->type)
|
|
fmtprint(&fmt, "type %s\n", part->type);
|
|
if(part->lines)
|
|
fmtprint(&fmt, "lines %d\n", part->lines);
|
|
if(part->filename)
|
|
fmtprint(&fmt, "filename %s\n", part->filename);
|
|
s = fmtstrflush(&fmt);
|
|
if(s == nil)
|
|
s = estrdup("");
|
|
*freeme = 1;
|
|
*pp = s;
|
|
return 0;
|
|
|
|
case Qheader:
|
|
if(part->hdr == nil)
|
|
return 0;
|
|
fmtstrinit(&fmt);
|
|
if(part == msg->part[0])
|
|
fmtprint(&fmt, "Date: %s", ctime(msg->date));
|
|
else
|
|
fmtprint(&fmt, "Date: %s\n", part->hdr->date);
|
|
addaddrs(&fmt, "To", part->hdr->to);
|
|
addaddrs(&fmt, "From", part->hdr->from);
|
|
if(part->hdr->from==nil
|
|
|| (part->hdr->sender && strcmp(part->hdr->sender, part->hdr->from) != 0))
|
|
addaddrs(&fmt, "Sender", part->hdr->sender);
|
|
if(part->hdr->from==nil
|
|
|| (part->hdr->replyto && strcmp(part->hdr->replyto, part->hdr->from) != 0))
|
|
addaddrs(&fmt, "Reply-To", part->hdr->replyto);
|
|
addaddrs(&fmt, "Subject", part->hdr->subject);
|
|
s = fmtstrflush(&fmt);
|
|
if(s == nil)
|
|
s = estrdup("");
|
|
*freeme = 1;
|
|
*pp = s;
|
|
return 0;
|
|
|
|
default:
|
|
werrstr(Egreg);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int
|
|
filldir(Dir *d, int type, Box *box, Msg *msg, Part *part)
|
|
{
|
|
int freeme, len;
|
|
char *s;
|
|
|
|
memset(d, 0, sizeof *d);
|
|
if(box){
|
|
d->atime = box->time;
|
|
d->mtime = box->time;
|
|
}else{
|
|
d->atime = t0;
|
|
d->mtime = t0;
|
|
}
|
|
d->uid = estrdup9p("upas");
|
|
d->gid = estrdup9p("upas");
|
|
d->muid = estrdup9p("upas");
|
|
d->qid = qid(type, box, msg, part);
|
|
|
|
switch(type){
|
|
case Qroot:
|
|
case Qbox:
|
|
case Qmsg:
|
|
d->mode = 0555|DMDIR;
|
|
if(box && !(box->flags&FlagNoInferiors))
|
|
d->mode = 0775|DMDIR;
|
|
break;
|
|
case Qctl:
|
|
case Qboxctl:
|
|
d->mode = 0222;
|
|
break;
|
|
case Qsearch:
|
|
d->mode = 0666;
|
|
break;
|
|
|
|
case Qflags:
|
|
d->mode = 0666;
|
|
goto msgfile;
|
|
default:
|
|
d->mode = 0444;
|
|
msgfile:
|
|
if(filedata(type, box, msg, part, &s, &len, &freeme, 0, ZQ) >= 0){
|
|
if(s){
|
|
if(len == -1)
|
|
d->length = strlen(s);
|
|
else
|
|
d->length = len;
|
|
if(freeme)
|
|
free(s);
|
|
}
|
|
}else if(type == Qraw && msg && part == msg->part[0])
|
|
d->length = msg->size;
|
|
break;
|
|
}
|
|
|
|
switch(type){
|
|
case Qroot:
|
|
d->name = estrdup9p("/");
|
|
break;
|
|
case Qbox:
|
|
if(box == nil){
|
|
werrstr(Enobox);
|
|
return -1;
|
|
}
|
|
d->name = estrdup9p(box->elem);
|
|
break;
|
|
case Qmsg:
|
|
if(msg == nil){
|
|
werrstr(Enomsg);
|
|
return -1;
|
|
}
|
|
if(part == nil || part == msg->part[0])
|
|
d->name = esmprint("%d", msg->id);
|
|
else
|
|
d->name = esmprint("%d", part->pix+1);
|
|
break;
|
|
case Qctl:
|
|
case Qboxctl:
|
|
d->name = estrdup9p("ctl");
|
|
break;
|
|
case Qsearch:
|
|
d->name = estrdup9p("search");
|
|
break;
|
|
default:
|
|
d->name = estrdup9p(nameoftype(type));
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
fsstat(Req *r)
|
|
{
|
|
int type;
|
|
Box *box;
|
|
Msg *msg;
|
|
Part *part;
|
|
|
|
type = parseqid(r->fid->qid, &box, &msg, &part);
|
|
if(filldir(&r->d, type, box, msg, part) < 0)
|
|
responderror(r);
|
|
else
|
|
respond(r, nil);
|
|
}
|
|
|
|
int
|
|
rootgen(int i, Dir *d, void *aux)
|
|
{
|
|
USED(aux);
|
|
|
|
if(i == 0)
|
|
return filldir(d, Qctl, nil, nil, nil);
|
|
i--;
|
|
if(i < rootbox->nsub)
|
|
return filldir(d, Qbox, rootbox->sub[i], nil, nil);
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
boxgen(int i, Dir *d, void *aux)
|
|
{
|
|
Box *box;
|
|
|
|
box = aux;
|
|
if(i == 0)
|
|
return filldir(d, Qboxctl, box, nil, nil);
|
|
i--;
|
|
if(i == 0)
|
|
return filldir(d, Qsearch, box, nil, nil);
|
|
if(i < box->nsub)
|
|
return filldir(d, Qbox, box->sub[i], nil, nil);
|
|
i -= box->nsub;
|
|
if(i < box->nmsg)
|
|
return filldir(d, Qmsg, box, box->msg[i], nil);
|
|
return -1;
|
|
}
|
|
|
|
static int msgdir[] = {
|
|
Qtype,
|
|
Qbody, Qbcc, Qcc, Qdate, Qflags, Qfrom, Qheader, Qinfo,
|
|
Qinreplyto, Qlines, Qmimeheader, Qmessageid,
|
|
Qraw, Qrawunix, Qrawbody, Qrawheader,
|
|
Qreplyto, Qsender, Qsubject, Qto,
|
|
Qunixdate, Qunixheader
|
|
};
|
|
static int mimemsgdir[] = {
|
|
Qtype,
|
|
Qbody, Qbcc, Qcc, Qdate, Qfrom, Qheader, Qinfo,
|
|
Qinreplyto, Qlines, Qmimeheader, Qmessageid,
|
|
Qraw, Qrawunix, Qrawbody, Qrawheader,
|
|
Qreplyto, Qsender, Qsubject, Qto
|
|
};
|
|
static int mimedir[] = {
|
|
Qtype,
|
|
Qbody,
|
|
Qmimeheader,
|
|
Qraw
|
|
};
|
|
|
|
int
|
|
msggen(int i, Dir *d, void *aux)
|
|
{
|
|
Box *box;
|
|
Msg *msg;
|
|
Part *part;
|
|
|
|
part = aux;
|
|
msg = part->msg;
|
|
box = msg->box;
|
|
if(part->ix == 0){
|
|
if(i < nelem(msgdir))
|
|
return filldir(d, msgdir[i], box, msg, part);
|
|
i -= nelem(msgdir);
|
|
}else if(part->type && strcmp(part->type, "message/rfc822") == 0){
|
|
if(i < nelem(mimemsgdir))
|
|
return filldir(d, mimemsgdir[i], box, msg, part);
|
|
i -= nelem(mimemsgdir);
|
|
}else{
|
|
if(i < nelem(mimedir))
|
|
return filldir(d, mimedir[i], box, msg, part);
|
|
i -= nelem(mimedir);
|
|
}
|
|
if(i < part->nsub)
|
|
return filldir(d, Qmsg, box, msg, part->sub[i]);
|
|
return -1;
|
|
}
|
|
|
|
enum
|
|
{
|
|
CMhangup
|
|
};
|
|
static Cmdtab ctltab[] =
|
|
{
|
|
CMhangup, "hangup", 2
|
|
};
|
|
|
|
enum
|
|
{
|
|
CMdelete,
|
|
CMrefresh,
|
|
CMreplied,
|
|
CMread,
|
|
CMsave,
|
|
CMjunk,
|
|
CMnonjunk
|
|
};
|
|
static Cmdtab boxctltab[] =
|
|
{
|
|
CMdelete, "delete", 0,
|
|
CMrefresh, "refresh", 1,
|
|
CMreplied, "replied", 0,
|
|
CMread, "read", 0,
|
|
CMsave, "save", 0,
|
|
CMjunk, "junk", 0,
|
|
CMnonjunk, "nonjunk", 0
|
|
};
|
|
|
|
static void
|
|
fsread(Req *r)
|
|
{
|
|
char *s;
|
|
int type, len, freeme;
|
|
Box *box;
|
|
Msg *msg;
|
|
Part *part;
|
|
|
|
switch(type = parseqid(r->fid->qid, &box, &msg, &part)){
|
|
case Qroot:
|
|
dirread9p(r, rootgen, nil);
|
|
respond(r, nil);
|
|
return;
|
|
|
|
case Qbox:
|
|
if(box == nil){
|
|
respond(r, Eboxgone);
|
|
return;
|
|
}
|
|
if(box->nmsg == 0)
|
|
imapcheckbox(imap, box);
|
|
parseqid(r->fid->qid, &box, &msg, &part);
|
|
if(box == nil){
|
|
respond(r, Eboxgone);
|
|
return;
|
|
}
|
|
dirread9p(r, boxgen, box);
|
|
respond(r, nil);
|
|
return;
|
|
|
|
case Qmsg:
|
|
if(msg == nil || part == nil){
|
|
respond(r, Emsggone);
|
|
return;
|
|
}
|
|
dirread9p(r, msggen, part);
|
|
respond(r, nil);
|
|
return;
|
|
|
|
case Qctl:
|
|
case Qboxctl:
|
|
respond(r, Egreg);
|
|
return;
|
|
|
|
case Qsearch:
|
|
readstr(r, r->fid->aux);
|
|
respond(r, nil);
|
|
return;
|
|
|
|
default:
|
|
if(filedata(type, box, msg, part, &s, &len, &freeme, 1, r->fid->qid) < 0){
|
|
responderror(r);
|
|
return;
|
|
}
|
|
if(s && len == -1)
|
|
len = strlen(s);
|
|
readbuf(r, s, len);
|
|
if(freeme)
|
|
free(s);
|
|
respond(r, nil);
|
|
return;
|
|
}
|
|
}
|
|
|
|
int
|
|
mkmsglist(Box *box, char **f, int nf, Msg ***mm)
|
|
{
|
|
int i, nm;
|
|
Msg **m;
|
|
|
|
m = emalloc(nf*sizeof m[0]);
|
|
nm = 0;
|
|
for(i=0; i<nf; i++)
|
|
if((m[nm] = msgbyid(box, atoi(f[i]))) != nil)
|
|
nm++;
|
|
*mm = m;
|
|
return nm;
|
|
}
|
|
|
|
static void
|
|
fswrite(Req *r)
|
|
{
|
|
int i, j, c, type, flag, unflag, flagset, f, reset;
|
|
Box *box;
|
|
Msg *msg;
|
|
Part *part;
|
|
Cmdbuf *cb;
|
|
Cmdtab *ct;
|
|
Msg **m;
|
|
int nm;
|
|
Fmt fmt;
|
|
|
|
r->ofcall.count = r->ifcall.count;
|
|
switch(type = parseqid(r->fid->qid, &box, &msg, &part)){
|
|
default:
|
|
respond(r, Egreg);
|
|
break;
|
|
|
|
case Qctl:
|
|
cb = parsecmd(r->ifcall.data, r->ifcall.count);
|
|
if((ct = lookupcmd(cb, ctltab, nelem(ctltab))) == nil){
|
|
respondcmderror(r, cb, "unknown message");
|
|
free(cb);
|
|
return;
|
|
}
|
|
r->ofcall.count = r->ifcall.count;
|
|
switch(ct->index){
|
|
case CMhangup:
|
|
imaphangup(imap, atoi(cb->f[1]));
|
|
respond(r, nil);
|
|
break;
|
|
default:
|
|
respond(r, Egreg);
|
|
break;
|
|
}
|
|
free(cb);
|
|
return;
|
|
|
|
case Qboxctl:
|
|
cb = parsecmd(r->ifcall.data, r->ifcall.count);
|
|
if((ct = lookupcmd(cb, boxctltab, nelem(boxctltab))) == nil){
|
|
respondcmderror(r, cb, "bad message");
|
|
free(cb);
|
|
return;
|
|
}
|
|
r->ofcall.count = r->ifcall.count;
|
|
switch(ct->index){
|
|
case CMsave:
|
|
if(cb->nf <= 2){
|
|
respondcmderror(r, cb, Ebadctl);
|
|
break;
|
|
}
|
|
nm = mkmsglist(box, cb->f+2, cb->nf-2, &m);
|
|
if(nm != cb->nf-2){
|
|
/* free(m); */
|
|
respond(r, Enomsg);
|
|
break;
|
|
}
|
|
if(nm > 0 && imapcopylist(imap, cb->f[1], m, nm) < 0)
|
|
responderror(r);
|
|
else
|
|
respond(r, nil);
|
|
free(m);
|
|
break;
|
|
|
|
case CMjunk:
|
|
flag = FlagJunk;
|
|
goto flagit;
|
|
case CMnonjunk:
|
|
flag = FlagNonJunk;
|
|
goto flagit;
|
|
case CMreplied:
|
|
flag = FlagReplied;
|
|
goto flagit;
|
|
case CMread:
|
|
flag = FlagSeen;
|
|
flagit:
|
|
if(cb->nf <= 1){
|
|
respondcmderror(r, cb, Ebadctl);
|
|
break;
|
|
}
|
|
nm = mkmsglist(box, cb->f+1, cb->nf-1, &m);
|
|
if(nm != cb->nf-1){
|
|
free(m);
|
|
respond(r, Enomsg);
|
|
break;
|
|
}
|
|
if(nm > 0 && imapflaglist(imap, +1, flag, m, nm) < 0)
|
|
responderror(r);
|
|
else
|
|
respond(r, nil);
|
|
free(m);
|
|
break;
|
|
|
|
case CMrefresh:
|
|
imapcheckbox(imap, box);
|
|
respond(r, nil);
|
|
break;
|
|
|
|
case CMdelete:
|
|
if(cb->nf <= 1){
|
|
respondcmderror(r, cb, Ebadctl);
|
|
break;
|
|
}
|
|
nm = mkmsglist(box, cb->f+1, cb->nf-1, &m);
|
|
if(nm > 0 && imapremovelist(imap, m, nm) < 0)
|
|
responderror(r);
|
|
else
|
|
respond(r, nil);
|
|
free(m);
|
|
break;
|
|
|
|
default:
|
|
respond(r, Egreg);
|
|
break;
|
|
}
|
|
free(cb);
|
|
return;
|
|
|
|
case Qflags:
|
|
if(msg == nil){
|
|
respond(r, Enomsg);
|
|
return;
|
|
}
|
|
cb = parsecmd(r->ifcall.data, r->ifcall.count);
|
|
flag = 0;
|
|
unflag = 0;
|
|
flagset = 0;
|
|
reset = 0;
|
|
for(i=0; i<cb->nf; i++){
|
|
f = 0;
|
|
c = cb->f[i][0];
|
|
if(c == '+' || c == '-')
|
|
cb->f[i]++;
|
|
for(j=0; j<nelem(flagtab); j++){
|
|
if(strcmp(flagtab[j].name, cb->f[i]) == 0){
|
|
f = flagtab[j].flag;
|
|
break;
|
|
}
|
|
}
|
|
if(f == 0){
|
|
respondcmderror(r, cb, "unknown flag %s", cb->f[i]);
|
|
free(cb);
|
|
return;
|
|
}
|
|
if(c == '+')
|
|
flag |= f;
|
|
else if(c == '-')
|
|
unflag |= f;
|
|
else
|
|
flagset |= f;
|
|
}
|
|
free(cb);
|
|
if((flagset!=0)+(unflag!=0)+(flag!=0) != 1){
|
|
respondcmderror(r, cb, Ebadctl);
|
|
return;
|
|
}
|
|
if(flag)
|
|
i = 1;
|
|
else if(unflag){
|
|
i = -1;
|
|
flag = unflag;
|
|
}else{
|
|
i = 0;
|
|
flag = flagset;
|
|
}
|
|
if(imapflaglist(imap, i, flag, &msg, 1) < 0)
|
|
responderror(r);
|
|
else
|
|
respond(r, nil);
|
|
return;
|
|
|
|
case Qsearch:
|
|
if(box == nil){
|
|
respond(r, Eboxgone);
|
|
return;
|
|
}
|
|
fmtstrinit(&fmt);
|
|
nm = imapsearchbox(imap, box, r->ifcall.data, &m);
|
|
for(i=0; i<nm; i++){
|
|
if(i>0)
|
|
fmtrune(&fmt, ' ');
|
|
fmtprint(&fmt, "%d", m[i]->id);
|
|
}
|
|
free(r->fid->aux);
|
|
r->fid->aux = fmtstrflush(&fmt);
|
|
respond(r, nil);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
fsopen(Req *r)
|
|
{
|
|
switch(qtype(r->fid->qid)){
|
|
case Qctl:
|
|
case Qboxctl:
|
|
if((r->ifcall.mode&~OTRUNC) != OWRITE){
|
|
respond(r, Eperm);
|
|
return;
|
|
}
|
|
respond(r, nil);
|
|
return;
|
|
|
|
case Qflags:
|
|
case Qsearch:
|
|
if((r->ifcall.mode&~OTRUNC) > ORDWR){
|
|
respond(r, Eperm);
|
|
return;
|
|
}
|
|
respond(r, nil);
|
|
return;
|
|
|
|
default:
|
|
if(r->ifcall.mode != OREAD){
|
|
respond(r, Eperm);
|
|
return;
|
|
}
|
|
respond(r, nil);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
fsflush(Req *r)
|
|
{
|
|
/*
|
|
* We only handle reads and writes outside the main loop,
|
|
* so we must be flushing one of those. In both cases it's
|
|
* okay to just ignore the results of the request, whenever
|
|
* they're ready.
|
|
*/
|
|
incref(&r->oldreq->ref);
|
|
respond(r->oldreq, "interrupted");
|
|
respond(r, nil);
|
|
}
|
|
|
|
static void
|
|
fsthread(void *v)
|
|
{
|
|
Req *r;
|
|
|
|
r = v;
|
|
switch(r->ifcall.type){
|
|
case Tread:
|
|
fsread(r);
|
|
break;
|
|
case Twrite:
|
|
fswrite(r);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
fsrecv(void *v)
|
|
{
|
|
Req *r;
|
|
|
|
while((r = recvp(fsreqchan)) != nil){
|
|
switch(r->ifcall.type){
|
|
case Tattach:
|
|
fsattach(r);
|
|
break;
|
|
case Tflush:
|
|
fsflush(r);
|
|
break;
|
|
case Topen:
|
|
fsopen(r);
|
|
break;
|
|
case Twalk:
|
|
fswalk(r);
|
|
break;
|
|
case Tstat:
|
|
fsstat(r);
|
|
break;
|
|
default:
|
|
threadcreate(fsthread, r, STACK);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
fssend(Req *r)
|
|
{
|
|
sendp(fsreqchan, r);
|
|
}
|
|
|
|
static void
|
|
fsdestroyfid(Fid *f)
|
|
{
|
|
free(f->aux);
|
|
}
|
|
|
|
void
|
|
fsinit0(void) /* bad planning - clash with lib9pclient */
|
|
{
|
|
t0 = time(0);
|
|
|
|
fs.attach = fssend;
|
|
fs.flush = fssend;
|
|
fs.open = fssend;
|
|
fs.walk = fssend;
|
|
fs.read = fssend;
|
|
fs.write = fssend;
|
|
fs.stat = fssend;
|
|
fs.destroyfid = fsdestroyfid;
|
|
|
|
rootqid = qid(Qroot, nil, nil, nil);
|
|
|
|
fsreqchan = chancreate(sizeof(void*), 0);
|
|
mailthread(fsrecv, nil);
|
|
}
|
|
|