plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

fs.c (22454B)


      1 /*
      2  * Mail file system.
      3  *
      4  * Serve the bulk of requests out of memory, so they can
      5  * be in the main loop (will never see their flushes).
      6  * Some requests do block and they get handled in
      7  * separate threads.  They're all okay to give up on
      8  * early, though, so we just respond saying interrupted
      9  * and then when they finish, silently discard the request.
     10 
     11 TO DO:
     12 
     13 	decode subject, etc.
     14 	decode body
     15 
     16 	digest
     17 	disposition
     18 	filename
     19 
     20 	ctl messages
     21 
     22 	fetch mail on demand
     23 
     24  */
     25 
     26 #include "a.h"
     27 
     28 enum
     29 {
     30 	/* directories */
     31 	Qroot,
     32 	Qbox,
     33 	Qmsg,
     34 
     35 	/* control files */
     36 	Qctl,
     37 	Qboxctl,
     38 	Qsearch,
     39 
     40 	/* message header - same order as struct Hdr */
     41 	Qdate,
     42 	Qsubject,
     43 	Qfrom,
     44 	Qsender,
     45 	Qreplyto,
     46 	Qto,
     47 	Qcc,
     48 	Qbcc,
     49 	Qinreplyto,
     50 	Qmessageid,
     51 
     52 	/* part data - same order as stuct Part */
     53 	Qtype,
     54 	Qidstr,
     55 	Qdesc,
     56 	Qencoding,	/* only here temporarily! */
     57 	Qcharset,
     58 	Qfilename,
     59 	Qraw,
     60 	Qrawheader,
     61 	Qrawbody,
     62 	Qmimeheader,
     63 
     64 	/* part numbers - same order as struct Part */
     65 	Qsize,
     66 	Qlines,
     67 
     68 	/* other message files */
     69 	Qbody,
     70 	Qheader,
     71 	Qdigest,
     72 	Qdisposition,
     73 	Qflags,
     74 	Qinfo,
     75 	Qrawunix,
     76 	Qunixdate,
     77 	Qunixheader,
     78 
     79 	Qfile0 = Qbody,
     80 	Qnfile = Qunixheader+1-Qfile0
     81 };
     82 
     83 static char Egreg[] = "gone postal";
     84 static char Enobox[] = "no such mailbox";
     85 static char Enomsg[] = "no such message";
     86 static char Eboxgone[] = "mailbox not available";
     87 static char Emsggone[] = "message not available";
     88 static char Eperm[] = "permission denied";
     89 static char Ebadctl[] = "bad control message";
     90 
     91 Channel *fsreqchan;
     92 Srv fs;
     93 Qid rootqid;
     94 ulong t0;
     95 
     96 #ifdef PLAN9PORT
     97 void
     98 responderror(Req *r)
     99 {
    100 	char e[ERRMAX];
    101 
    102 	rerrstr(e, sizeof e);
    103 	respond(r, e);
    104 }
    105 #endif
    106 
    107 int
    108 qtype(Qid q)
    109 {
    110 	return q.path&0x3F;
    111 }
    112 
    113 int
    114 qboxid(Qid q)
    115 {
    116 	return (q.path>>40)&0xFFFF;
    117 }
    118 
    119 int
    120 qmsgid(Qid q)
    121 {
    122 	return ((q.path>>32)&0xFF000000) | ((q.path>>16)&0xFFFFFF);
    123 }
    124 
    125 int
    126 qpartid(Qid q)
    127 {
    128 	return ((q.path>>6)&0x3FF);
    129 }
    130 
    131 Qid
    132 qid(int ctl, Box *box, Msg *msg, Part *part)
    133 {
    134 	Qid q;
    135 
    136 	q.type = 0;
    137 	if(ctl == Qroot || ctl == Qbox || ctl == Qmsg)
    138 		q.type = QTDIR;
    139 	q.path = (vlong)((msg ? msg->id : 0)&0xFF000000)<<32;
    140 	q.path |= (vlong)((msg ? msg->id : 0)&0xFFFFFF)<<16;
    141 	q.path |= (vlong)((box ? box->id : 0)&0xFFFF)<<40;
    142 	q.path |= ((part ? part->ix : 0)&0x3FF)<<6;
    143 	q.path |= ctl&0x3F;
    144 	q.vers = box ? box->validity : 0;
    145 	return q;
    146 }
    147 
    148 int
    149 parseqid(Qid q, Box **box, Msg **msg, Part **part)
    150 {
    151 	*msg = nil;
    152 	*part = nil;
    153 
    154 	*box = boxbyid(qboxid(q));
    155 	if(*box){
    156 		*msg = msgbyid(*box, qmsgid(q));
    157 	}
    158 	if(*msg)
    159 		*part = partbyid(*msg, qpartid(q));
    160 	return qtype(q);
    161 }
    162 
    163 static struct {
    164 	int type;
    165 	char *name;
    166 } typenames[] = {
    167 	Qbody,			"body",
    168 	Qbcc,			"bcc",
    169 	Qcc,				"cc",
    170 	Qdate,			"date",
    171 	Qfilename,		"filename",
    172 	Qflags,			"flags",
    173 	Qfrom,			"from",
    174 	Qheader,			"header",
    175 	Qinfo,			"info",
    176 	Qinreplyto,		"inreplyto",
    177 	Qlines,			"lines",
    178 	Qmimeheader,	"mimeheader",
    179 	Qmessageid,		"messageid",
    180 	Qraw,			"raw",
    181 	Qrawunix,		"rawunix",
    182 	Qrawbody,		"rawbody",
    183 	Qrawheader,		"rawheader",
    184 	Qreplyto,			"replyto",
    185 	Qsender,			"sender",
    186 	Qsubject,		"subject",
    187 	Qto,				"to",
    188 	Qtype,			"type",
    189 	Qunixdate,		"unixdate",
    190 	Qunixheader,		"unixheader",
    191 	Qidstr,			"idstr",
    192 	Qdesc,			"desc",
    193 	Qencoding,		"encoding",
    194 	Qcharset,		"charset"
    195 };
    196 
    197 char*
    198 nameoftype(int t)
    199 {
    200 	int i;
    201 
    202 	for(i=0; i<nelem(typenames); i++)
    203 		if(typenames[i].type == t)
    204 			return typenames[i].name;
    205 	return "???";
    206 }
    207 
    208 int
    209 typeofname(char *name)
    210 {
    211 	int i;
    212 
    213 	for(i=0; i<nelem(typenames); i++)
    214 		if(strcmp(typenames[i].name, name) == 0)
    215 			return typenames[i].type;
    216 	return 0;
    217 }
    218 
    219 static void
    220 fsattach(Req *r)
    221 {
    222 	r->fid->qid = rootqid;
    223 	r->ofcall.qid = rootqid;
    224 	respond(r, nil);
    225 }
    226 
    227 static int
    228 isnumber(char *s)
    229 {
    230 	int n;
    231 
    232 	if(*s < '1' || *s > '9')
    233 		return 0;
    234 	n = strtol(s, &s, 10);
    235 	if(*s != 0)
    236 		return 0;
    237 	return n;
    238 }
    239 
    240 static char*
    241 fswalk1(Fid *fid, char *name, void *arg)
    242 {
    243 	int a, type;
    244 	Box *b, *box;
    245 	Msg *msg;
    246 	Part *p, *part;
    247 
    248 	USED(arg);
    249 
    250 	switch(type = parseqid(fid->qid, &box, &msg, &part)){
    251 	case Qroot:
    252 		if(strcmp(name, "..") == 0)
    253 			return nil;
    254 		if(strcmp(name, "ctl") == 0){
    255 			fid->qid = qid(Qctl, nil, nil, nil);
    256 			return nil;
    257 		}
    258 		if((box = boxbyname(name)) != nil){
    259 			fid->qid = qid(Qbox, box, nil, nil);
    260 			return nil;
    261 		}
    262 		break;
    263 
    264 	case Qbox:
    265 		/*
    266 		 * Would be nice if .. could work even if the box is gone,
    267 		 * but we don't know how deep the directory was.
    268 		 */
    269 		if(box == nil)
    270 			return Eboxgone;
    271 		if(strcmp(name, "..") == 0){
    272 			if((box = box->parent) == nil){
    273 				fid->qid = rootqid;
    274 				return nil;
    275 			}
    276 			fid->qid = qid(Qbox, box, nil, nil);
    277 			return nil;
    278 		}
    279 		if(strcmp(name, "ctl") == 0){
    280 			fid->qid = qid(Qboxctl, box, nil, nil);
    281 			return nil;
    282 		}
    283 		if(strcmp(name, "search") == 0){
    284 			fid->qid = qid(Qsearch, box, nil, nil);
    285 			return nil;
    286 		}
    287 		if((b = subbox(box, name)) != nil){
    288 			fid->qid = qid(Qbox, b, nil, nil);
    289 			return nil;
    290 		}
    291 		if((a = isnumber(name)) != 0){
    292 			if((msg = msgbyid(box, a)) == nil){
    293 				return Enomsg;
    294 			}
    295 			fid->qid = qid(Qmsg, box, msg, nil);
    296 			return nil;
    297 		}
    298 		break;
    299 
    300 	case Qmsg:
    301 		if(strcmp(name, "..") == 0){
    302 			if(part == msg->part[0]){
    303 				fid->qid = qid(Qbox, box, nil, nil);
    304 				return nil;
    305 			}
    306 			fid->qid = qid(Qmsg, box, msg, part->parent);
    307 			return nil;
    308 		}
    309 		if((type = typeofname(name)) > 0){
    310 			/* XXX - should check that type makes sense (see msggen) */
    311 			fid->qid = qid(type, box, msg, part);
    312 			return nil;
    313 		}
    314 		if((a = isnumber(name)) != 0){
    315 			if((p = subpart(part, a-1)) != nil){
    316 				fid->qid = qid(Qmsg, box, msg, p);
    317 				return nil;
    318 			}
    319 		}
    320 		break;
    321 	}
    322 	return "not found";
    323 }
    324 
    325 static void
    326 fswalk(Req *r)
    327 {
    328 	walkandclone(r, fswalk1, nil, nil);
    329 }
    330 
    331 static struct {
    332 	int flag;
    333 	char *name;
    334 } flagtab[] = {
    335 	FlagJunk,			"junk",
    336 	FlagNonJunk,		"notjunk",
    337 	FlagReplied,	"replied",
    338 	FlagFlagged,		"flagged",
    339 /*	FlagDeleted,		"deleted", */
    340 	FlagDraft,		"draft",
    341 	FlagSeen,			"seen"
    342 };
    343 
    344 static void
    345 addaddrs(Fmt *fmt, char *prefix, char *addrs)
    346 {
    347 	char **f;
    348 	int i, nf, inquote;
    349 	char *p, *sep;
    350 
    351 	if(addrs == nil)
    352 		return;
    353 	addrs = estrdup(addrs);
    354 	nf = 0;
    355 	inquote = 0;
    356 	for(p=addrs; *p; p++){
    357 		if(*p == ' ' && !inquote)
    358 			nf++;
    359 		if(*p == '\'')
    360 			inquote = !inquote;
    361 	}
    362 	nf += 10;
    363 	f = emalloc(nf*sizeof f[0]);
    364 	nf = tokenize(addrs, f, nf);
    365 	fmtprint(fmt, "%s:", prefix);
    366 	sep = " ";
    367 	for(i=0; i+1<nf; i+=2){
    368 		if(f[i][0])
    369 			fmtprint(fmt, "%s%s <%s>", sep, f[i], f[i+1]);
    370 		else
    371 			fmtprint(fmt, "%s%s", sep, f[i+1]);
    372 		sep = ", ";
    373 	}
    374 	fmtprint(fmt, "\n");
    375 	free(addrs);
    376 }
    377 
    378 static void
    379 mkbody(Part *p, Qid q)
    380 {
    381 	char *t;
    382 	int len;
    383 
    384 	USED(q);
    385 	if(p->msg->part[0] == p)
    386 		t = p->rawbody;
    387 	else
    388 		t = p->raw;
    389 	if(t == nil)
    390 		return;
    391 
    392 	len = -1;
    393 	if(p->encoding && cistrcmp(p->encoding, "quoted-printable") == 0)
    394 		t = decode(QuotedPrintable, t, &len);
    395 	else if(p->encoding && cistrcmp(p->encoding, "base64") == 0)
    396 		t = decode(Base64, t, &len);
    397 	else
    398 		t = estrdup(t);
    399 
    400 	if(p->charset){
    401 		t = tcs(p->charset, t);
    402 		len = -1;
    403 	}
    404 	p->body = t;
    405 	if(len == -1)
    406 		p->nbody = strlen(t);
    407 	else
    408 		p->nbody = len;
    409 }
    410 
    411 static Qid ZQ;
    412 
    413 static int
    414 filedata(int type, Box *box, Msg *msg, Part *part, char **pp, int *len, int *freeme, int force, Qid q)
    415 {
    416 	int i, inquote, n, t;
    417 	char *from, *s;
    418 	static char buf[256];
    419 	Fmt fmt;
    420 
    421 	*pp = nil;
    422 	*freeme = 0;
    423 	if(len)
    424 		*len = -1;
    425 
    426 	if(msg == nil || part == nil){
    427 		werrstr(Emsggone);
    428 		return -1;
    429 	}
    430 	switch(type){
    431 	case Qdate:
    432 	case Qsubject:
    433 	case Qfrom:
    434 	case Qsender:
    435 	case Qreplyto:
    436 	case Qto:
    437 	case Qcc:
    438 	case Qbcc:
    439 	case Qinreplyto:
    440 	case Qmessageid:
    441 		if(part->hdr == nil){
    442 			werrstr(Emsggone);
    443 			return -1;
    444 		}
    445 		*pp = ((char**)&part->hdr->date)[type-Qdate];
    446 		return 0;
    447 
    448 	case Qunixdate:
    449 		strcpy(buf, ctime(msg->date));
    450 		*pp = buf;
    451 		return 0;
    452 
    453 	case Qunixheader:
    454 		if(part->hdr == nil){
    455 			werrstr(Emsggone);
    456 			return -1;
    457 		}
    458 		from = part->hdr->from;
    459 		if(from == nil)
    460 			from = "???";
    461 		else{
    462 			inquote = 0;
    463 			for(; *from; from++){
    464 				if(*from == '\'')
    465 					inquote = !inquote;
    466 				if(!inquote && *from == ' '){
    467 					from++;
    468 					break;
    469 				}
    470 			}
    471 			if(*from == 0)
    472 				from = part->hdr->from;
    473 		}
    474 		n = snprint(buf, sizeof buf, "From %s %s", from, ctime(msg->date));
    475 		if(n+1 < sizeof buf){
    476 			*pp = buf;
    477 			return 0;
    478 		}
    479 		fmtstrinit(&fmt);
    480 		fmtprint(&fmt, "From %s %s", from, ctime(msg->date));
    481 		s = fmtstrflush(&fmt);
    482 		if(s){
    483 			*pp = s;
    484 			*freeme = 1;
    485 		}else
    486 			*pp = buf;
    487 		return 0;
    488 
    489 	case Qtype:
    490 	case Qidstr:
    491 	case Qdesc:
    492 	case Qencoding:
    493 	case Qcharset:
    494 	case Qfilename:
    495 	case Qraw:
    496 	case Qrawheader:
    497 	case Qrawbody:
    498 	case Qmimeheader:
    499 		*pp = ((char**)&part->type)[type-Qtype];
    500 		if(*pp == nil && force){
    501 			switch(type){
    502 			case Qraw:
    503 				imapfetchraw(imap, part);
    504 				break;
    505 			case Qrawheader:
    506 				imapfetchrawheader(imap, part);
    507 				break;
    508 			case Qrawbody:
    509 				imapfetchrawbody(imap, part);
    510 				break;
    511 			case Qmimeheader:
    512 				imapfetchrawmime(imap, part);
    513 				break;
    514 			default:
    515 				return 0;
    516 			}
    517 			/*
    518 			 * We ran fetchsomething, which might have changed
    519 			 * the mailbox contents.  Msg might even be gone.
    520 			 */
    521 			t = parseqid(q, &box, &msg, &part);
    522 			if(t != type || msg == nil || part == nil)
    523 				return 0;
    524 			*pp = ((char**)&part->type)[type-Qtype];
    525 		}
    526 		return 0;
    527 
    528 	case Qbody:
    529 		if(part->body){
    530 			*pp = part->body;
    531 			if(len)
    532 				*len = part->nbody;
    533 			return 0;
    534 		}
    535 		if(!force)
    536 			return 0;
    537 		if(part->rawbody == nil){
    538 			if(part->msg->part[0] == part)
    539 				imapfetchrawbody(imap, part);
    540 			else
    541 				imapfetchraw(imap, part);
    542 			t = parseqid(q, &box, &msg, &part);
    543 			if(t != type || msg == nil || part == nil)
    544 				return 0;
    545 		}
    546 		mkbody(part, q);
    547 		*pp = part->body;
    548 		if(len)
    549 			*len = part->nbody;
    550 		return 0;
    551 
    552 	case Qsize:
    553 	case Qlines:
    554 		n = ((uint*)&part->size)[type-Qsize];
    555 		snprint(buf, sizeof buf, "%d", n);
    556 		*pp = buf;
    557 		return 0;
    558 
    559 	case Qflags:
    560 		s = buf;
    561 		*s = 0;
    562 		for(i=0; i<nelem(flagtab); i++){
    563 			if(msg->flags&flagtab[i].flag){
    564 				if(s > buf)
    565 					*s++ = ' ';
    566 				strcpy(s, flagtab[i].name);
    567 				s += strlen(s);
    568 			}
    569 		}
    570 		*pp = buf;
    571 		return 0;
    572 
    573 	case Qinfo:
    574 		fmtstrinit(&fmt);
    575 		if(part == msg->part[0]){
    576 			if(msg->date)
    577 				fmtprint(&fmt, "unixdate %ud %s", msg->date, ctime(msg->date));
    578 			if(msg->flags){
    579 				filedata(Qflags, box, msg, part, pp, nil, freeme, 0, ZQ);
    580 				fmtprint(&fmt, "flags %s\n", buf);
    581 			}
    582 		}
    583 		if(part->hdr){
    584 			if(part->hdr->digest)
    585 				fmtprint(&fmt, "digest %s\n", part->hdr->digest);
    586 			if(part->hdr->from)
    587 				fmtprint(&fmt, "from %s\n", part->hdr->from);
    588 			if(part->hdr->to)
    589 				fmtprint(&fmt, "to %s\n", part->hdr->to);
    590 			if(part->hdr->cc)
    591 				fmtprint(&fmt, "cc %s\n", part->hdr->cc);
    592 			if(part->hdr->replyto)
    593 				fmtprint(&fmt, "replyto %s\n", part->hdr->replyto);
    594 			if(part->hdr->bcc)
    595 				fmtprint(&fmt, "bcc %s\n", part->hdr->bcc);
    596 			if(part->hdr->inreplyto)
    597 				fmtprint(&fmt, "inreplyto %s\n", part->hdr->inreplyto);
    598 			if(part->hdr->date)
    599 				fmtprint(&fmt, "date %s\n", part->hdr->date);
    600 			if(part->hdr->sender)
    601 				fmtprint(&fmt, "sender %s\n", part->hdr->sender);
    602 			if(part->hdr->messageid)
    603 				fmtprint(&fmt, "messageid %s\n", part->hdr->messageid);
    604 			if(part->hdr->subject)
    605 				fmtprint(&fmt, "subject %s\n", part->hdr->subject);
    606 		}
    607 		if(part->type)
    608 			fmtprint(&fmt, "type %s\n", part->type);
    609 		if(part->lines)
    610 			fmtprint(&fmt, "lines %d\n", part->lines);
    611 		if(part->filename)
    612 			fmtprint(&fmt, "filename %s\n", part->filename);
    613 		s = fmtstrflush(&fmt);
    614 		if(s == nil)
    615 			s = estrdup("");
    616 		*freeme = 1;
    617 		*pp = s;
    618 		return 0;
    619 
    620 	case Qheader:
    621 		if(part->hdr == nil)
    622 			return 0;
    623 		fmtstrinit(&fmt);
    624 		if(part == msg->part[0])
    625 			fmtprint(&fmt, "Date: %s", ctime(msg->date));
    626 		else
    627 			fmtprint(&fmt, "Date: %s\n", part->hdr->date);
    628 		addaddrs(&fmt, "To", part->hdr->to);
    629 		addaddrs(&fmt, "From", part->hdr->from);
    630 		if(part->hdr->from==nil
    631 		|| (part->hdr->sender && strcmp(part->hdr->sender, part->hdr->from) != 0))
    632 			addaddrs(&fmt, "Sender", part->hdr->sender);
    633 		if(part->hdr->from==nil
    634 		|| (part->hdr->replyto && strcmp(part->hdr->replyto, part->hdr->from) != 0))
    635 			addaddrs(&fmt, "Reply-To", part->hdr->replyto);
    636 		fmtprint(&fmt, "Subject: %s\n", part->hdr->subject);
    637 		s = fmtstrflush(&fmt);
    638 		if(s == nil)
    639 			s = estrdup("");
    640 		*freeme = 1;
    641 		*pp = s;
    642 		return 0;
    643 
    644 	default:
    645 		werrstr(Egreg);
    646 		return -1;
    647 	}
    648 }
    649 
    650 int
    651 filldir(Dir *d, int type, Box *box, Msg *msg, Part *part)
    652 {
    653 	int freeme, len;
    654 	char *s;
    655 
    656 	memset(d, 0, sizeof *d);
    657 	if(box){
    658 		d->atime = box->time;
    659 		d->mtime = box->time;
    660 	}else{
    661 		d->atime = t0;
    662 		d->mtime = t0;
    663 	}
    664 	d->uid = estrdup9p("upas");
    665 	d->gid = estrdup9p("upas");
    666 	d->muid = estrdup9p("upas");
    667 	d->qid = qid(type, box, msg, part);
    668 
    669 	switch(type){
    670 	case Qroot:
    671 	case Qbox:
    672 	case Qmsg:
    673 		d->mode = 0555|DMDIR;
    674 		if(box && !(box->flags&FlagNoInferiors))
    675 			d->mode = 0775|DMDIR;
    676 		break;
    677 	case Qctl:
    678 	case Qboxctl:
    679 		d->mode = 0222;
    680 		break;
    681 	case Qsearch:
    682 		d->mode = 0666;
    683 		break;
    684 
    685 	case Qflags:
    686 		d->mode = 0666;
    687 		goto msgfile;
    688 	default:
    689 		d->mode = 0444;
    690 	msgfile:
    691 		if(filedata(type, box, msg, part, &s, &len, &freeme, 0, ZQ) >= 0){
    692 			if(s){
    693 				if(len == -1)
    694 					d->length = strlen(s);
    695 				else
    696 					d->length = len;
    697 				if(freeme)
    698 					free(s);
    699 			}
    700 		}else if(type == Qraw && msg && part == msg->part[0])
    701 			d->length = msg->size;
    702 		break;
    703 	}
    704 
    705 	switch(type){
    706 	case Qroot:
    707 		d->name = estrdup9p("/");
    708 		break;
    709 	case Qbox:
    710 		if(box == nil){
    711 			werrstr(Enobox);
    712 			return -1;
    713 		}
    714 		d->name = estrdup9p(box->elem);
    715 		break;
    716 	case Qmsg:
    717 		if(msg == nil){
    718 			werrstr(Enomsg);
    719 			return -1;
    720 		}
    721 		if(part == nil || part == msg->part[0])
    722 			d->name = esmprint("%d", msg->id);
    723 		else
    724 			d->name = esmprint("%d", part->pix+1);
    725 		break;
    726 	case Qctl:
    727 	case Qboxctl:
    728 		d->name = estrdup9p("ctl");
    729 		break;
    730 	case Qsearch:
    731 		d->name = estrdup9p("search");
    732 		break;
    733 	default:
    734 		d->name = estrdup9p(nameoftype(type));
    735 		break;
    736 	}
    737 	return 0;
    738 }
    739 
    740 static void
    741 fsstat(Req *r)
    742 {
    743 	int type;
    744 	Box *box;
    745 	Msg *msg;
    746 	Part *part;
    747 
    748 	type = parseqid(r->fid->qid, &box, &msg, &part);
    749 	if(filldir(&r->d, type, box, msg, part) < 0)
    750 		responderror(r);
    751 	else
    752 		respond(r, nil);
    753 }
    754 
    755 int
    756 rootgen(int i, Dir *d, void *aux)
    757 {
    758 	USED(aux);
    759 
    760 	if(i == 0)
    761 		return filldir(d, Qctl, nil, nil, nil);
    762 	i--;
    763 	if(i < rootbox->nsub)
    764 		return filldir(d, Qbox, rootbox->sub[i], nil, nil);
    765 	return -1;
    766 }
    767 
    768 int
    769 boxgen(int i, Dir *d, void *aux)
    770 {
    771 	Box *box;
    772 
    773 	box = aux;
    774 	if(i == 0)
    775 		return filldir(d, Qboxctl, box, nil, nil);
    776 	i--;
    777 	if(i == 0)
    778 		return filldir(d, Qsearch, box, nil, nil);
    779 	i--;
    780 	if(i < box->nsub)
    781 		return filldir(d, Qbox, box->sub[i], nil, nil);
    782 	i -= box->nsub;
    783 	if(i < box->nmsg)
    784 		return filldir(d, Qmsg, box, box->msg[i], nil);
    785 	return -1;
    786 }
    787 
    788 static int msgdir[] = {
    789 	Qtype,
    790 	Qbody, Qbcc, Qcc, Qdate, Qflags, Qfrom, Qheader, Qinfo,
    791 	Qinreplyto, Qlines, Qmimeheader, Qmessageid,
    792 	Qraw, Qrawunix, Qrawbody, Qrawheader,
    793 	Qreplyto, Qsender, Qsubject, Qto,
    794 	Qunixdate, Qunixheader
    795 };
    796 static int mimemsgdir[] = {
    797 	Qtype,
    798 	Qbody, Qbcc, Qcc, Qdate, Qfrom, Qheader, Qinfo,
    799 	Qinreplyto, Qlines, Qmimeheader, Qmessageid,
    800 	Qraw, Qrawunix, Qrawbody, Qrawheader,
    801 	Qreplyto, Qsender, Qsubject, Qto
    802 };
    803 static int mimedir[] = {
    804 	Qtype,
    805 	Qbody,
    806 	Qfilename,
    807 	Qcharset,
    808 	Qmimeheader,
    809 	Qraw
    810 };
    811 
    812 int
    813 msggen(int i, Dir *d, void *aux)
    814 {
    815 	Box *box;
    816 	Msg *msg;
    817 	Part *part;
    818 
    819 	part = aux;
    820 	msg = part->msg;
    821 	box = msg->box;
    822 	if(part->ix == 0){
    823 		if(i < nelem(msgdir))
    824 			return filldir(d, msgdir[i], box, msg, part);
    825 		i -= nelem(msgdir);
    826 	}else if(part->type && strcmp(part->type, "message/rfc822") == 0){
    827 		if(i < nelem(mimemsgdir))
    828 			return filldir(d, mimemsgdir[i], box, msg, part);
    829 		i -= nelem(mimemsgdir);
    830 	}else{
    831 		if(i < nelem(mimedir))
    832 			return filldir(d, mimedir[i], box, msg, part);
    833 		i -= nelem(mimedir);
    834 	}
    835 	if(i < part->nsub)
    836 		return filldir(d, Qmsg, box, msg, part->sub[i]);
    837 	return -1;
    838 }
    839 
    840 enum
    841 {
    842 	CMhangup
    843 };
    844 static Cmdtab ctltab[] =
    845 {
    846 	CMhangup, "hangup", 2
    847 };
    848 
    849 enum
    850 {
    851 	CMdelete,
    852 	CMrefresh,
    853 	CMreplied,
    854 	CMread,
    855 	CMsave,
    856 	CMjunk,
    857 	CMnonjunk
    858 };
    859 static Cmdtab boxctltab[] =
    860 {
    861 	CMdelete,	"delete",	0,
    862 	CMrefresh,	"refresh", 1,
    863 	CMreplied,	"replied", 0,
    864 	CMread,		"read", 0,
    865 	CMsave,		"save", 0,
    866 	CMjunk,		"junk", 0,
    867 	CMnonjunk,	"nonjunk", 0
    868 };
    869 
    870 static void
    871 fsread(Req *r)
    872 {
    873 	char *s;
    874 	int type, len, freeme;
    875 	Box *box;
    876 	Msg *msg;
    877 	Part *part;
    878 
    879 	switch(type = parseqid(r->fid->qid, &box, &msg, &part)){
    880 	case Qroot:
    881 		dirread9p(r, rootgen, nil);
    882 		respond(r, nil);
    883 		return;
    884 
    885 	case Qbox:
    886 		if(box == nil){
    887 			respond(r, Eboxgone);
    888 			return;
    889 		}
    890 		if(box->nmsg == 0)
    891 			imapcheckbox(imap, box);
    892 		parseqid(r->fid->qid, &box, &msg, &part);
    893 		if(box == nil){
    894 			respond(r, Eboxgone);
    895 			return;
    896 		}
    897 		dirread9p(r, boxgen, box);
    898 		respond(r, nil);
    899 		return;
    900 
    901 	case Qmsg:
    902 		if(msg == nil || part == nil){
    903 			respond(r, Emsggone);
    904 			return;
    905 		}
    906 		dirread9p(r, msggen, part);
    907 		respond(r, nil);
    908 		return;
    909 
    910 	case Qctl:
    911 	case Qboxctl:
    912 		respond(r, Egreg);
    913 		return;
    914 
    915 	case Qsearch:
    916 		readstr(r, r->fid->aux);
    917 		respond(r, nil);
    918 		return;
    919 
    920 	default:
    921 		if(filedata(type, box, msg, part, &s, &len, &freeme, 1, r->fid->qid) < 0){
    922 			responderror(r);
    923 			return;
    924 		}
    925 		if(s && len == -1)
    926 			len = strlen(s);
    927 		readbuf(r, s, len);
    928 		if(freeme)
    929 			free(s);
    930 		respond(r, nil);
    931 		return;
    932 	}
    933 }
    934 
    935 int
    936 mkmsglist(Box *box, char **f, int nf, Msg ***mm)
    937 {
    938 	int i, nm;
    939 	Msg **m;
    940 
    941 	m = emalloc(nf*sizeof m[0]);
    942 	nm = 0;
    943 	for(i=0; i<nf; i++)
    944 		if((m[nm] = msgbyid(box, atoi(f[i]))) != nil)
    945 			nm++;
    946 	*mm = m;
    947 	return nm;
    948 }
    949 
    950 static void
    951 fswrite(Req *r)
    952 {
    953 	int i, j, c, type, flag, unflag, flagset, f;
    954 	Box *box;
    955 	Msg *msg;
    956 	Part *part;
    957 	Cmdbuf *cb;
    958 	Cmdtab *ct;
    959 	Msg **m;
    960 	int nm;
    961 	Fmt fmt;
    962 
    963 	r->ofcall.count = r->ifcall.count;
    964 	switch(type = parseqid(r->fid->qid, &box, &msg, &part)){
    965 	default:
    966 		respond(r, Egreg);
    967 		break;
    968 
    969 	case Qctl:
    970 		cb = parsecmd(r->ifcall.data, r->ifcall.count);
    971 		if((ct = lookupcmd(cb, ctltab, nelem(ctltab))) == nil){
    972 			respondcmderror(r, cb, "unknown message");
    973 			free(cb);
    974 			return;
    975 		}
    976 		r->ofcall.count = r->ifcall.count;
    977 		switch(ct->index){
    978 		case CMhangup:
    979 			imaphangup(imap, atoi(cb->f[1]));
    980 			respond(r, nil);
    981 			break;
    982 		default:
    983 			respond(r, Egreg);
    984 			break;
    985 		}
    986 		free(cb);
    987 		return;
    988 
    989 	case Qboxctl:
    990 		cb = parsecmd(r->ifcall.data, r->ifcall.count);
    991 		if((ct = lookupcmd(cb, boxctltab, nelem(boxctltab))) == nil){
    992 			respondcmderror(r, cb, "bad message");
    993 			free(cb);
    994 			return;
    995 		}
    996 		r->ofcall.count = r->ifcall.count;
    997 		switch(ct->index){
    998 		case CMsave:
    999 			if(cb->nf <= 2){
   1000 				respondcmderror(r, cb, Ebadctl);
   1001 				break;
   1002 			}
   1003 			nm = mkmsglist(box, cb->f+2, cb->nf-2, &m);
   1004 			if(nm != cb->nf-2){
   1005 			/*	free(m); */
   1006 				respond(r, Enomsg);
   1007 				break;
   1008 			}
   1009 			if(nm > 0 && imapcopylist(imap, cb->f[1], m, nm) < 0)
   1010 				responderror(r);
   1011 			else
   1012 				respond(r, nil);
   1013 			free(m);
   1014 			break;
   1015 
   1016 		case CMjunk:
   1017 			flag = FlagJunk;
   1018 			goto flagit;
   1019 		case CMnonjunk:
   1020 			flag = FlagNonJunk;
   1021 			goto flagit;
   1022 		case CMreplied:
   1023 			flag = FlagReplied;
   1024 			goto flagit;
   1025 		case CMread:
   1026 			flag = FlagSeen;
   1027 		flagit:
   1028 			if(cb->nf <= 1){
   1029 				respondcmderror(r, cb, Ebadctl);
   1030 				break;
   1031 			}
   1032 			nm = mkmsglist(box, cb->f+1, cb->nf-1, &m);
   1033 			if(nm != cb->nf-1){
   1034 				free(m);
   1035 				respond(r, Enomsg);
   1036 				break;
   1037 			}
   1038 			if(nm > 0 && imapflaglist(imap, +1, flag, m, nm) < 0)
   1039 				responderror(r);
   1040 			else
   1041 				respond(r, nil);
   1042 			free(m);
   1043 			break;
   1044 
   1045 		case CMrefresh:
   1046 			imapcheckbox(imap, box);
   1047 			respond(r, nil);
   1048 			break;
   1049 
   1050 		case CMdelete:
   1051 			if(cb->nf <= 1){
   1052 				respondcmderror(r, cb, Ebadctl);
   1053 				break;
   1054 			}
   1055 			nm = mkmsglist(box, cb->f+1, cb->nf-1, &m);
   1056 			if(nm > 0 && imapremovelist(imap, m, nm) < 0)
   1057 				responderror(r);
   1058 			else
   1059 				respond(r, nil);
   1060 			free(m);
   1061 			break;
   1062 
   1063 		default:
   1064 			respond(r, Egreg);
   1065 			break;
   1066 		}
   1067 		free(cb);
   1068 		return;
   1069 
   1070 	case Qflags:
   1071 		if(msg == nil){
   1072 			respond(r, Enomsg);
   1073 			return;
   1074 		}
   1075 		cb = parsecmd(r->ifcall.data, r->ifcall.count);
   1076 		flag = 0;
   1077 		unflag = 0;
   1078 		flagset = 0;
   1079 		for(i=0; i<cb->nf; i++){
   1080 			f = 0;
   1081 			c = cb->f[i][0];
   1082 			if(c == '+' || c == '-')
   1083 				cb->f[i]++;
   1084 			for(j=0; j<nelem(flagtab); j++){
   1085 				if(strcmp(flagtab[j].name, cb->f[i]) == 0){
   1086 					f = flagtab[j].flag;
   1087 					break;
   1088 				}
   1089 			}
   1090 			if(f == 0){
   1091 				respondcmderror(r, cb, "unknown flag %s", cb->f[i]);
   1092 				free(cb);
   1093 				return;
   1094 			}
   1095 			if(c == '+')
   1096 				flag |= f;
   1097 			else if(c == '-')
   1098 				unflag |= f;
   1099 			else
   1100 				flagset |= f;
   1101 		}
   1102 		free(cb);
   1103 		if((flagset!=0)+(unflag!=0)+(flag!=0) != 1){
   1104 			respondcmderror(r, cb, Ebadctl);
   1105 			return;
   1106 		}
   1107 		if(flag)
   1108 			i = 1;
   1109 		else if(unflag){
   1110 			i = -1;
   1111 			flag = unflag;
   1112 		}else{
   1113 			i = 0;
   1114 			flag = flagset;
   1115 		}
   1116 		if(imapflaglist(imap, i, flag, &msg, 1) < 0)
   1117 			responderror(r);
   1118 		else
   1119 			respond(r, nil);
   1120 		return;
   1121 
   1122 	case Qsearch:
   1123 		if(box == nil){
   1124 			respond(r, Eboxgone);
   1125 			return;
   1126 		}
   1127 		fmtstrinit(&fmt);
   1128 		nm = imapsearchbox(imap, box, r->ifcall.data, &m);
   1129 		for(i=0; i<nm; i++){
   1130 			if(i>0)
   1131 				fmtrune(&fmt, ' ');
   1132 			fmtprint(&fmt, "%d", m[i]->id);
   1133 		}
   1134 		free(r->fid->aux);
   1135 		r->fid->aux = fmtstrflush(&fmt);
   1136 		respond(r, nil);
   1137 		return;
   1138 	}
   1139 }
   1140 
   1141 static void
   1142 fsopen(Req *r)
   1143 {
   1144 	switch(qtype(r->fid->qid)){
   1145 	case Qctl:
   1146 	case Qboxctl:
   1147 		if((r->ifcall.mode&~OTRUNC) != OWRITE){
   1148 			respond(r, Eperm);
   1149 			return;
   1150 		}
   1151 		respond(r, nil);
   1152 		return;
   1153 
   1154 	case Qflags:
   1155 	case Qsearch:
   1156 		if((r->ifcall.mode&~OTRUNC) > ORDWR){
   1157 			respond(r, Eperm);
   1158 			return;
   1159 		}
   1160 		respond(r, nil);
   1161 		return;
   1162 
   1163 	default:
   1164 		if(r->ifcall.mode != OREAD){
   1165 			respond(r, Eperm);
   1166 			return;
   1167 		}
   1168 		respond(r, nil);
   1169 		return;
   1170 	}
   1171 }
   1172 
   1173 static void
   1174 fsflush(Req *r)
   1175 {
   1176 	/*
   1177 	 * We only handle reads and writes outside the main loop,
   1178 	 * so we must be flushing one of those.  In both cases it's
   1179 	 * okay to just ignore the results of the request, whenever
   1180 	 * they're ready.
   1181 	 */
   1182 	incref(&r->oldreq->ref);
   1183 	respond(r->oldreq, "interrupted");
   1184 	respond(r, nil);
   1185 }
   1186 
   1187 static void
   1188 fsthread(void *v)
   1189 {
   1190 	Req *r;
   1191 
   1192 	r = v;
   1193 	switch(r->ifcall.type){
   1194 	case Tread:
   1195 		fsread(r);
   1196 		break;
   1197 	case Twrite:
   1198 		fswrite(r);
   1199 		break;
   1200 	}
   1201 }
   1202 
   1203 static void
   1204 fsrecv(void *v)
   1205 {
   1206 	Req *r;
   1207 
   1208 	USED(v);
   1209 	while((r = recvp(fsreqchan)) != nil){
   1210 		switch(r->ifcall.type){
   1211 		case Tattach:
   1212 			fsattach(r);
   1213 			break;
   1214 		case Tflush:
   1215 			fsflush(r);
   1216 			break;
   1217 		case Topen:
   1218 			fsopen(r);
   1219 			break;
   1220 		case Twalk:
   1221 			fswalk(r);
   1222 			break;
   1223 		case Tstat:
   1224 			fsstat(r);
   1225 			break;
   1226 		default:
   1227 			threadcreate(fsthread, r, STACK);
   1228 			break;
   1229 		}
   1230 	}
   1231 }
   1232 
   1233 static void
   1234 fssend(Req *r)
   1235 {
   1236 	sendp(fsreqchan, r);
   1237 }
   1238 
   1239 static void
   1240 fsdestroyfid(Fid *f)
   1241 {
   1242 	free(f->aux);
   1243 }
   1244 
   1245 void
   1246 fsinit0(void)	/* bad planning - clash with lib9pclient */
   1247 {
   1248 	t0 = time(0);
   1249 
   1250 	fs.attach = fssend;
   1251 	fs.flush = fssend;
   1252 	fs.open = fssend;
   1253 	fs.walk = fssend;
   1254 	fs.read = fssend;
   1255 	fs.write = fssend;
   1256 	fs.stat = fssend;
   1257 	fs.destroyfid = fsdestroyfid;
   1258 
   1259 	rootqid = qid(Qroot, nil, nil, nil);
   1260 
   1261 	fsreqchan = chancreate(sizeof(void*), 0);
   1262 	mailthread(fsrecv, nil);
   1263 }