xref: /plan9-contrib/sys/src/cmd/upas/ned/nedmail.c (revision 1936bb650459bace06c38a45b60888b47e5cd459)
1 #include "common.h"
2 #include <ctype.h>
3 #include <plumb.h>
4 
5 typedef struct Message Message;
6 typedef struct Ctype Ctype;
7 typedef struct Cmd Cmd;
8 
9 char	root[Pathlen];
10 char	mbname[Elemlen];
11 int	rootlen;
12 int	didopen;
13 char	*user;
14 char	wd[2048];
15 String	*mbpath;
16 int	natural;
17 int	doflush;
18 
19 int interrupted;
20 
21 struct Message {
22 	Message	*next;
23 	Message	*prev;
24 	Message	*cmd;
25 	Message	*child;
26 	Message	*parent;
27 	String	*path;
28 	int	id;
29 	int	len;
30 	int	fileno;	// number of directory
31 	String	*info;
32 	char	*from;
33 	char	*to;
34 	char	*cc;
35 	char	*replyto;
36 	char	*date;
37 	char	*subject;
38 	char	*type;
39 	char	*disposition;
40 	char	*filename;
41 	char	deleted;
42 	char	stored;
43 };
44 
45 Message top;
46 
47 struct Ctype {
48 	char	*type;
49 	char 	*ext;
50 	int	display;
51 	char	*plumbdest;
52 	Ctype	*next;
53 };
54 
55 Ctype ctype[] = {
56 	{ "text/plain",			"txt",	1,	0	},
57 	{ "text/html",			"htm",	1,	0	},
58 	{ "text/html",			"html",	1,	0	},
59 	{ "text/tab-separated-values",	"tsv",	1,	0	},
60 	{ "text/richtext",		"rtx",	1,	0	},
61 	{ "text/rtf",			"rtf",	1,	0	},
62 	{ "text",			"txt",	1,	0	},
63 	{ "message/rfc822",		"msg",	0,	0	},
64 	{ "image/bmp",			"bmp",	0,	"image"	},
65 	{ "image/jpeg",			"jpg",	0,	"image"	},
66 	{ "image/gif",			"gif",	0,	"image"	},
67 	{ "image/png",			"png",	0,	"image"	},
68 	{ "application/pdf",		"pdf",	0,	"postscript"	},
69 	{ "application/postscript",	"ps",	0,	"postscript"	},
70 	{ "application/",		0,	0,	0	},
71 	{ "image/",			0,	0,	0	},
72 	{ "multipart/",			"mul",	0,	0	},
73 
74 };
75 
76 Message*	acmd(Cmd*, Message*);
77 Message*	bcmd(Cmd*, Message*);
78 Message*	dcmd(Cmd*, Message*);
79 Message*	eqcmd(Cmd*, Message*);
80 Message*	hcmd(Cmd*, Message*);
81 Message*	Hcmd(Cmd*, Message*);
82 Message*	helpcmd(Cmd*, Message*);
83 Message*	icmd(Cmd*, Message*);
84 Message*	pcmd(Cmd*, Message*);
85 Message*	qcmd(Cmd*, Message*);
86 Message*	rcmd(Cmd*, Message*);
87 Message*	scmd(Cmd*, Message*);
88 Message*	ucmd(Cmd*, Message*);
89 Message*	wcmd(Cmd*, Message*);
90 Message*	xcmd(Cmd*, Message*);
91 Message*	ycmd(Cmd*, Message*);
92 Message*	pipecmd(Cmd*, Message*);
93 Message*	rpipecmd(Cmd*, Message*);
94 Message*	bangcmd(Cmd*, Message*);
95 Message*	Pcmd(Cmd*, Message*);
96 Message*	mcmd(Cmd*, Message*);
97 Message*	fcmd(Cmd*, Message*);
98 Message*	quotecmd(Cmd*, Message*);
99 
100 struct {
101 	char		*cmd;
102 	int		args;
103 	Message*	(*f)(Cmd*, Message*);
104 	char		*help;
105 } cmdtab[] = {
106 	{ "a",	1,	acmd,	"a        reply to sender and recipients" },
107 	{ "A",	1,	acmd,	"A        reply to sender and recipients with copy" },
108 	{ "b",	0,	bcmd,	"b        print the next 10 headers" },
109 	{ "d",	0,	dcmd,	"d        mark for deletion" },
110 	{ "f",	0,	fcmd,	"f        file message by from address" },
111 	{ "h",	0,	hcmd,	"h        print elided message summary (,h for all)" },
112 	{ "help", 0,	helpcmd, "help     print this info" },
113 	{ "H",	0,	Hcmd,	"H        print message's MIME structure " },
114 	{ "i",	0,	icmd,	"i        incorporate new mail" },
115 	{ "m",	1,	mcmd,	"m addr   forward mail" },
116 	{ "M",	1,	mcmd,	"M addr   forward mail with message" },
117 	{ "p",	0,	pcmd,	"p        print the processed message" },
118 	{ "P",	0,	Pcmd,	"P        print the raw message" },
119 	{ "\"",	0,	quotecmd, "\"        print a quoted version of msg" },
120 	{ "q",	0,	qcmd,	"q        exit and remove all deleted mail" },
121 	{ "r",	1,	rcmd,	"r [addr] reply to sender plus any addrs specified" },
122 	{ "rf",	1,	rcmd,	"rf [addr]file message and reply" },
123 	{ "R",	1,	rcmd,	"R [addr] reply including copy of message" },
124 	{ "Rf",	1,	rcmd,	"Rf [addr]file message and reply with copy" },
125 	{ "s",	1,	scmd,	"s file   append raw message to file" },
126 	{ "u",	0,	ucmd,	"u        remove deletion mark" },
127 	{ "w",	1,	wcmd,	"w file   store message contents as file" },
128 	{ "x",	0,	xcmd,	"x        exit without flushing deleted messages" },
129 	{ "y",	0,	ycmd,	"y        synchronize with mail box" },
130 	{ "=",	1,	eqcmd,	"=        print current message number" },
131 	{ "|",	1,	pipecmd, "|cmd     pipe message body to a command" },
132 	{ "||",	1,	rpipecmd, "||cmd     pipe raw message to a command" },
133 	{ "!",	1,	bangcmd, "!cmd     run a command" },
134 	{ nil,	0,	nil, 	nil },
135 };
136 
137 enum
138 {
139 	NARG=	32,
140 };
141 
142 struct Cmd {
143 	Message	*msgs;
144 	Message	*(*f)(Cmd*, Message*);
145 	int	an;
146 	char	*av[NARG];
147 	int	delete;
148 };
149 
150 Biobuf out;
151 int startedfs;
152 int reverse;
153 int longestfrom = 12;
154 
155 String*		file2string(String*, char*);
156 int		dir2message(Message*, int);
157 int		filelen(String*, char*);
158 String*		extendpath(String*, char*);
159 void		snprintheader(char*, int, Message*);
160 void		cracktime(char*, char*, int);
161 int		cistrncmp(char*, char*, int);
162 int		cistrcmp(char*, char*);
163 Reprog*		parsesearch(char**);
164 char*		parseaddr(char**, Message*, Message*, Message*, Message**);
165 char*		parsecmd(char*, Cmd*, Message*, Message*);
166 char*		readline(char*, char*, int);
167 void		messagecount(Message*);
168 void		system(char*, char**, int);
169 void		mkid(String*, Message*);
170 int		switchmb(char*, char*);
171 void		closemb(void);
172 int		lineize(char*, char**, int);
173 int		rawsearch(Message*, Reprog*);
174 Message*	dosingleton(Message*, char*);
175 String*		rooted(String*);
176 int		plumb(Message*, Ctype*);
177 String*		addrecolon(char*);
178 void		exitfs(char*);
179 Message*	flushdeleted(Message*);
180 
181 void
182 usage(void)
183 {
184 	fprint(2, "usage: %s [-nr] [-f mailfile] [-s mailfile]\n", argv0);
185 	fprint(2, "       %s -c dir\n", argv0);
186 	exits("usage");
187 }
188 
189 void
190 catchnote(void*, char *note)
191 {
192 	if(strstr(note, "interrupt") != nil){
193 		interrupted = 1;
194 		noted(NCONT);
195 	}
196 	noted(NDFLT);
197 }
198 
199 char *
200 plural(int n)
201 {
202 	if (n == 1)
203 		return "";
204 
205 	return "s";
206 }
207 
208 void
209 main(int argc, char **argv)
210 {
211 	Message *cur, *m, *x;
212 	char cmdline[4*1024];
213 	Cmd cmd;
214 	Ctype *cp;
215 	int n, cflag;
216 	char *av[4];
217 	String *prompt;
218 	char *err, *file, *singleton;
219 
220 	quotefmtinstall();
221 	Binit(&out, 1, OWRITE);
222 
223 	file = nil;
224 	singleton = nil;
225 	reverse = 1;
226 	cflag = 0;
227 	ARGBEGIN {
228 	case 'c':
229 		cflag = 1;
230 		break;
231 	case 'f':
232 		file = EARGF(usage());
233 		break;
234 	case 's':
235 		singleton = EARGF(usage());
236 		break;
237 	case 'r':
238 		reverse = 0;
239 		break;
240 	case 'n':
241 		natural = 1;
242 		reverse = 0;
243 		break;
244 	default:
245 		usage();
246 		break;
247 	} ARGEND;
248 
249 	user = getlog();
250 	if(user == nil || *user == 0)
251 		sysfatal("can't read user name");
252 
253 	if(cflag){
254 		if(argc > 0)
255 			creatembox(user, argv[0]);
256 		else
257 			creatembox(user, nil);
258 		exits(0);
259 	}
260 
261 	if(argc)
262 		usage();
263 
264 	if(access("/mail/fs/ctl", 0) < 0){
265 		startedfs = 1;
266 		av[0] = "fs";
267 		av[1] = "-p";
268 		av[2] = 0;
269 		system("/bin/upas/fs", av, -1);
270 	}
271 
272 	switchmb(file, singleton);
273 
274 	top.path = s_copy(root);
275 
276 	for(cp = ctype; cp < ctype+nelem(ctype)-1; cp++)
277 		cp->next = cp+1;
278 
279 	if(singleton != nil){
280 		cur = dosingleton(&top, singleton);
281 		if(cur == nil){
282 			Bprint(&out, "no message\n");
283 			exitfs(0);
284 		}
285 		pcmd(nil, cur);
286 	} else {
287 		cur = &top;
288 		n = dir2message(&top, reverse);
289 		if(n < 0)
290 			sysfatal("can't read %s", s_to_c(top.path));
291 		Bprint(&out, "%d message%s\n", n, plural(n));
292 	}
293 
294 
295 	notify(catchnote);
296 	prompt = s_new();
297 	for(;;){
298 		s_reset(prompt);
299 		if(cur == &top)
300 			s_append(prompt, ": ");
301 		else {
302 			mkid(prompt, cur);
303 			s_append(prompt, ": ");
304 		}
305 
306 		// leave space at the end of cmd line in case parsecmd needs to
307 		// add a space after a '|' or '!'
308 		if(readline(s_to_c(prompt), cmdline, sizeof(cmdline)-1) == nil)
309 			break;
310 		err = parsecmd(cmdline, &cmd, top.child, cur);
311 		if(err != nil){
312 			Bprint(&out, "!%s\n", err);
313 			continue;
314 		}
315 		if(singleton != nil && cmd.f == icmd){
316 			Bprint(&out, "!illegal command\n");
317 			continue;
318 		}
319 		interrupted = 0;
320 		if(cmd.msgs == nil || cmd.msgs == &top){
321 			x = (*cmd.f)(&cmd, &top);
322 			if(x != nil)
323 				cur = x;
324 		} else for(m = cmd.msgs; m != nil; m = m->cmd){
325 			x = m;
326 			if(cmd.delete){
327 				dcmd(&cmd, x);
328 
329 				// dp acts differently than all other commands
330 				// since its an old lesk idiom that people love.
331 				// it deletes the current message, moves the current
332 				// pointer ahead one and prints.
333 				if(cmd.f == pcmd){
334 					if(x->next == nil){
335 						Bprint(&out, "!address\n");
336 						cur = x;
337 						break;
338 					} else
339 						x = x->next;
340 				}
341 			}
342 			x = (*cmd.f)(&cmd, x);
343 			if(x != nil)
344 				cur = x;
345 			if(interrupted)
346 				break;
347 			if(singleton != nil && (cmd.delete || cmd.f == dcmd))
348 				qcmd(nil, nil);
349 		}
350 		if(doflush)
351 			cur = flushdeleted(cur);
352 	}
353 	qcmd(nil, nil);
354 }
355 
356 //
357 // read the message info
358 //
359 Message*
360 file2message(Message *parent, char *name)
361 {
362 	Message *m;
363 	String *path;
364 	char *f[10];
365 
366 	m = mallocz(sizeof(Message), 1);
367 	if(m == nil)
368 		return nil;
369 	m->path = path = extendpath(parent->path, name);
370 	m->fileno = atoi(name);
371 	m->info = file2string(path, "info");
372 	lineize(s_to_c(m->info), f, nelem(f));
373 	m->from = f[0];
374 	m->to = f[1];
375 	m->cc = f[2];
376 	m->replyto = f[3];
377 	m->date = f[4];
378 	m->subject = f[5];
379 	m->type = f[6];
380 	m->disposition = f[7];
381 	m->filename = f[8];
382 	m->len = filelen(path, "raw");
383 	if(strstr(m->type, "multipart") != nil || strcmp(m->type, "message/rfc822") == 0)
384 		dir2message(m, 0);
385 	m->parent = parent;
386 
387 	return m;
388 }
389 
390 void
391 freemessage(Message *m)
392 {
393 	Message *nm, *next;
394 
395 	for(nm = m->child; nm != nil; nm = next){
396 		next = nm->next;
397 		freemessage(nm);
398 	}
399 	s_free(m->path);
400 	s_free(m->info);
401 	free(m);
402 }
403 
404 //
405 //  read a directory into a list of messages
406 //
407 int
408 dir2message(Message *parent, int reverse)
409 {
410 	int i, n, fd, highest, newmsgs;
411 	Dir *d;
412 	Message *first, *last, *m;
413 
414 	fd = open(s_to_c(parent->path), OREAD);
415 	if(fd < 0)
416 		return -1;
417 
418 	// count current entries
419 	first = parent->child;
420 	highest = newmsgs = 0;
421 	for(last = parent->child; last != nil && last->next != nil; last = last->next)
422 		if(last->fileno > highest)
423 			highest = last->fileno;
424 	if(last != nil)
425 		if(last->fileno > highest)
426 			highest = last->fileno;
427 
428 	n = dirreadall(fd, &d);
429 	for(i = 0; i < n; i++){
430 		if((d[i].qid.type & QTDIR) == 0)
431 			continue;
432 		if(atoi(d[i].name) <= highest)
433 			continue;
434 		m = file2message(parent, d[i].name);
435 		if(m == nil)
436 			break;
437 		newmsgs++;
438 		if(reverse){
439 			m->next = first;
440 			if(first != nil)
441 				first->prev = m;
442 			first = m;
443 		} else {
444 			if(first == nil)
445 				first = m;
446 			else
447 				last->next = m;
448 			m->prev = last;
449 			last = m;
450 		}
451 	}
452 	free(d);
453 	close(fd);
454 	parent->child = first;
455 
456 	// renumber and file longest from
457 	i = 1;
458 	longestfrom = 12;
459 	for(m = first; m != nil; m = m->next){
460 		m->id = natural ? m->fileno : i++;
461 		n = strlen(m->from);
462 		if(n > longestfrom)
463 			longestfrom = n;
464 	}
465 
466 	return newmsgs;
467 }
468 
469 //
470 //  point directly to a message
471 //
472 Message*
473 dosingleton(Message *parent, char *path)
474 {
475 	char *p, *np;
476 	Message *m;
477 
478 	// walk down to message and read it
479 	if(strlen(path) < rootlen)
480 		return nil;
481 	if(path[rootlen] != '/')
482 		return nil;
483 	p = path+rootlen+1;
484 	np = strchr(p, '/');
485 	if(np != nil)
486 		*np = 0;
487 	m = file2message(parent, p);
488 	if(m == nil)
489 		return nil;
490 	parent->child = m;
491 	m->id = 1;
492 
493 	// walk down to requested component
494 	while(np != nil){
495 		*np = '/';
496 		np = strchr(np+1, '/');
497 		if(np != nil)
498 			*np = 0;
499 		for(m = m->child; m != nil; m = m->next)
500 			if(strcmp(path, s_to_c(m->path)) == 0)
501 				return m;
502 		if(m == nil)
503 			return nil;
504 	}
505 	return m;
506 }
507 
508 //
509 //  read a file into a string
510 //
511 String*
512 file2string(String *dir, char *file)
513 {
514 	String *s;
515 	int fd, n, m;
516 
517 	s = extendpath(dir, file);
518 	fd = open(s_to_c(s), OREAD);
519 	s_grow(s, 512);			/* avoid multiple reads on info files */
520 	s_reset(s);
521 	if(fd < 0)
522 		return s;
523 
524 	for(;;){
525 		n = s->end - s->ptr;
526 		if(n == 0){
527 			s_grow(s, 128);
528 			continue;
529 		}
530 		m = read(fd, s->ptr, n);
531 		if(m <= 0)
532 			break;
533 		s->ptr += m;
534 		if(m < n)
535 			break;
536 	}
537 	s_terminate(s);
538 	close(fd);
539 
540 	return s;
541 }
542 
543 //
544 //  get the length of a file
545 //
546 int
547 filelen(String *dir, char *file)
548 {
549 	String *path;
550 	Dir *d;
551 	int rv;
552 
553 	path = extendpath(dir, file);
554 	d = dirstat(s_to_c(path));
555 	if(d == nil){
556 		s_free(path);
557 		return -1;
558 	}
559 	s_free(path);
560 	rv = d->length;
561 	free(d);
562 	return rv;
563 }
564 
565 //
566 //  walk the path name an element
567 //
568 String*
569 extendpath(String *dir, char *name)
570 {
571 	String *path;
572 
573 	if(strcmp(s_to_c(dir), ".") == 0)
574 		path = s_new();
575 	else {
576 		path = s_copy(s_to_c(dir));
577 		s_append(path, "/");
578 	}
579 	s_append(path, name);
580 	return path;
581 }
582 
583 int
584 cistrncmp(char *a, char *b, int n)
585 {
586 	while(n-- > 0){
587 		if(tolower(*a++) != tolower(*b++))
588 			return -1;
589 	}
590 	return 0;
591 }
592 
593 int
594 cistrcmp(char *a, char *b)
595 {
596 	for(;;){
597 		if(tolower(*a) != tolower(*b++))
598 			return -1;
599 		if(*a++ == 0)
600 			break;
601 	}
602 	return 0;
603 }
604 
605 char*
606 nosecs(char *t)
607 {
608 	char *p;
609 
610 	p = strchr(t, ':');
611 	if(p == nil)
612 		return t;
613 	p = strchr(p+1, ':');
614 	if(p != nil)
615 		*p = 0;
616 	return t;
617 }
618 
619 char *months[12] =
620 {
621 	"jan", "feb", "mar", "apr", "may", "jun",
622 	"jul", "aug", "sep", "oct", "nov", "dec"
623 };
624 
625 int
626 month(char *m)
627 {
628 	int i;
629 
630 	for(i = 0; i < 12; i++)
631 		if(cistrcmp(m, months[i]) == 0)
632 			return i+1;
633 	return 1;
634 }
635 
636 enum
637 {
638 	Yearsecs= 365*24*60*60
639 };
640 
641 void
642 cracktime(char *d, char *out, int len)
643 {
644 	char in[64];
645 	char *f[6];
646 	int n;
647 	Tm tm;
648 	long now, then;
649 	char *dtime;
650 
651 	*out = 0;
652 	if(d == nil)
653 		return;
654 	strncpy(in, d, sizeof(in));
655 	in[sizeof(in)-1] = 0;
656 	n = getfields(in, f, 6, 1, " \t\r\n");
657 	if(n != 6){
658 		// unknown style
659 		snprint(out, 16, "%10.10s", d);
660 		return;
661 	}
662 	now = time(0);
663 	memset(&tm, 0, sizeof tm);
664 	if(strchr(f[0], ',') != nil && strchr(f[4], ':') != nil){
665 		// 822 style
666 		tm.year = atoi(f[3])-1900;
667 		tm.mon = month(f[2]);
668 		tm.mday = atoi(f[1]);
669 		dtime = nosecs(f[4]);
670 		then = tm2sec(&tm);
671 	} else if(strchr(f[3], ':') != nil){
672 		// unix style
673 		tm.year = atoi(f[5])-1900;
674 		tm.mon = month(f[1]);
675 		tm.mday = atoi(f[2]);
676 		dtime = nosecs(f[3]);
677 		then = tm2sec(&tm);
678 	} else {
679 		then = now;
680 		tm = *localtime(now);
681 		dtime = "";
682 	}
683 
684 	if(now - then < Yearsecs/2)
685 		snprint(out, len, "%2d/%2.2d %s", tm.mon, tm.mday, dtime);
686 	else
687 		snprint(out, len, "%2d/%2.2d  %4d", tm.mon, tm.mday, tm.year+1900);
688 }
689 
690 Ctype*
691 findctype(Message *m)
692 {
693 	char *p;
694 	char ftype[128];
695 	int n, pfd[2];
696 	Ctype *a, *cp;
697 	static Ctype nulltype	= { "", 0, 0, 0 };
698 	static Ctype bintype 	= { "application/octet-stream", "bin", 0, 0 };
699 
700 	for(cp = ctype; cp; cp = cp->next)
701 		if(strncmp(cp->type, m->type, strlen(cp->type)) == 0)
702 			return cp;
703 
704 /*	use file(1) for any unknown mimetypes
705  *
706  *	if (strcmp(m->type, bintype.type) != 0)
707  *		return &nulltype;
708  */
709 	if(pipe(pfd) < 0)
710 		return &bintype;
711 
712 	*ftype = 0;
713 	switch(fork()){
714 	case -1:
715 		break;
716 	case 0:
717 		close(pfd[1]);
718 		close(0);
719 		dup(pfd[0], 0);
720 		close(1);
721 		dup(pfd[0], 1);
722 		execl("/bin/file", "file", "-m", s_to_c(extendpath(m->path, "body")), nil);
723 		exits(0);
724 	default:
725 		close(pfd[0]);
726 		n = read(pfd[1], ftype, sizeof(ftype));
727 		if(n > 0)
728 			ftype[n] = 0;
729 		close(pfd[1]);
730 		waitpid();
731 		break;
732 	}
733 
734 	if (*ftype=='\0' || (p = strchr(ftype, '/')) == nil)
735 		return &bintype;
736 	*p++ = 0;
737 
738 	a = mallocz(sizeof(Ctype), 1);
739 	a->type = strdup(ftype);
740 	a->ext = strdup(p);
741 	a->display = 0;
742 	a->plumbdest = strdup(ftype);
743 	for(cp = ctype; cp->next; cp = cp->next)
744 		continue;
745 	cp->next = a;
746 	a->next = nil;
747 	return a;
748 }
749 
750 void
751 mkid(String *s, Message *m)
752 {
753 	char buf[32];
754 
755 	if(m->parent != &top){
756 		mkid(s, m->parent);
757 		s_append(s, ".");
758 	}
759 	sprint(buf, "%d", m->id);
760 	s_append(s, buf);
761 }
762 
763 void
764 snprintheader(char *buf, int len, Message *m)
765 {
766 	char timebuf[32];
767 	String *id;
768 	char *p, *q;
769 
770 	// create id
771 	id = s_new();
772 	mkid(id, m);
773 
774 	if(*m->from == 0){
775 		// no from
776 		snprint(buf, len, "%-3s    %s %6d  %s",
777 			s_to_c(id),
778 			m->type,
779 			m->len,
780 			m->filename);
781 	} else if(*m->subject){
782 		q = p = strdup(m->subject);
783 		while(*p == ' ')
784 			p++;
785 		if(strlen(p) > 50)
786 			p[50] = 0;
787 		cracktime(m->date, timebuf, sizeof(timebuf));
788 		snprint(buf, len, "%-3s %c%c%c %6d  %11.11s %-*.*s %s",
789 			s_to_c(id),
790 			m->child ? 'H' : ' ',
791 			m->deleted ? 'd' : ' ',
792 			m->stored ? 's' : ' ',
793 			m->len,
794 			timebuf,
795 			longestfrom, longestfrom, m->from,
796 			p);
797 		free(q);
798 	} else {
799 		cracktime(m->date, timebuf, sizeof(timebuf));
800 		snprint(buf, len, "%-3s %c%c%c %6d  %11.11s %s",
801 			s_to_c(id),
802 			m->child ? 'H' : ' ',
803 			m->deleted ? 'd' : ' ',
804 			m->stored ? 's' : ' ',
805 			m->len,
806 			timebuf,
807 			m->from);
808 	}
809 	s_free(id);
810 }
811 
812 char *spaces = "                                                                    ";
813 
814 void
815 snprintHeader(char *buf, int len, int indent, Message *m)
816 {
817 	String *id;
818 	char typeid[64];
819 	char *p, *e;
820 
821 	// create id
822 	id = s_new();
823 	mkid(id, m);
824 
825 	e = buf + len;
826 
827 	snprint(typeid, sizeof typeid, "%s    %s", s_to_c(id), m->type);
828 	if(indent < 6)
829 		p = seprint(buf, e, "%-32s %-6d ", typeid, m->len);
830 	else
831 		p = seprint(buf, e, "%-64s %-6d ", typeid, m->len);
832 	if(m->filename && *m->filename)
833 		p = seprint(p, e, "(file,%s)", m->filename);
834 	if(m->from && *m->from)
835 		p = seprint(p, e, "(from,%s)", m->from);
836 	if(m->subject && *m->subject)
837 		seprint(p, e, "(subj,%s)", m->subject);
838 
839 	s_free(id);
840 }
841 
842 char sstring[256];
843 
844 //	cmd := range cmd ' ' arg-list ;
845 //	range := address
846 //		| address ',' address
847 //		| 'g' search ;
848 //	address := msgno
849 //		| search ;
850 //	msgno := number
851 //		| number '/' msgno ;
852 //	search := '/' string '/'
853 //		| '%' string '%' ;
854 //
855 Reprog*
856 parsesearch(char **pp)
857 {
858 	char *p, *np;
859 	int c, n;
860 
861 	p = *pp;
862 	c = *p++;
863 	np = strchr(p, c);
864 	if(np != nil){
865 		*np++ = 0;
866 		*pp = np;
867 	} else {
868 		n = strlen(p);
869 		*pp = p + n;
870 	}
871 	if(*p == 0)
872 		p = sstring;
873 	else{
874 		strncpy(sstring, p, sizeof(sstring));
875 		sstring[sizeof(sstring)-1] = 0;
876 	}
877 	return regcomp(p);
878 }
879 
880 static char *
881 num2msg(Message **mp, int sign, int n, Message *first, Message *cur)
882 {
883 	Message *m;
884 
885 	m = nil;
886 	switch(sign){
887 	case 0:
888 		for(m = first; m != nil; m = m->next)
889 			if(m->id == n)
890 				break;
891 		break;
892 	case -1:
893 		if(cur != &top)
894 			for(m = cur; m != nil && n > 0; n--)
895 				m = m->prev;
896 		break;
897 	case 1:
898 		if(cur == &top){
899 			n--;
900 			cur = first;
901 		}
902 		for(m = cur; m != nil && n > 0; n--)
903 			m = m->next;
904 		break;
905 	}
906 	if(m == nil)
907 		return "address";
908 	*mp = m;
909 	return nil;
910 }
911 
912 char*
913 parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp)
914 {
915 	int n;
916 	Message *m;
917 	char *p, *err;
918 	Reprog *prog;
919 	int c, sign;
920 	char buf[256];
921 
922 	*mp = nil;
923 	p = *pp;
924 
925 	if(*p == '+'){
926 		sign = 1;
927 		p++;
928 		*pp = p;
929 	} else if(*p == '-'){
930 		sign = -1;
931 		p++;
932 		*pp = p;
933 	} else
934 		sign = 0;
935 
936 	/*
937 	 * TODO: verify & install this.
938 	 * make + and - mean +1 and -1, as in ed.  then -,.d won't
939 	 * delete all messages up to the current one.  - geoff
940 	 */
941 	if(sign && (!isascii(*p) || !isdigit(*p))) {
942 		err = num2msg(mp, sign, 1, first, cur);
943 		if (err != nil)
944 			return err;
945 	}
946 
947 	switch(*p){
948 	default:
949 		if(sign){
950 			n = 1;
951 			goto number;
952 		}
953 		*mp = unspec;
954 		break;
955 	case '0': case '1': case '2': case '3': case '4':
956 	case '5': case '6': case '7': case '8': case '9':
957 		n = strtoul(p, pp, 10);
958 		if(n == 0){
959 			if(sign)
960 				*mp = cur;
961 			else
962 				*mp = &top;
963 			break;
964 		}
965 		/* fall through */
966 	number:
967 		err = num2msg(mp, sign, n, first, cur);
968 		if (err != nil)
969 			return err;
970 		break;
971 	case '%':
972 	case '/':
973 	case '?':
974 		c = *p;
975 		prog = parsesearch(pp);
976 		if(prog == nil)
977 			return "badly formed regular expression";
978 		m = nil;
979 		switch(c){
980 		case '%':
981 			for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
982 				if(rawsearch(m, prog))
983 					break;
984 			}
985 			break;
986 		case '/':
987 			for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
988 				snprintheader(buf, sizeof(buf), m);
989 				if(regexec(prog, buf, nil, 0))
990 					break;
991 			}
992 			break;
993 		case '?':
994 			for(m = cur == &top ? nil : cur->prev; m != nil; m = m->prev){
995 				snprintheader(buf, sizeof(buf), m);
996 				if(regexec(prog, buf, nil, 0))
997 					break;
998 			}
999 			break;
1000 		}
1001 		if(m == nil)
1002 			return "search";
1003 		*mp = m;
1004 		free(prog);
1005 		break;
1006 	case '$':
1007 		for(m = first; m != nil && m->next != nil; m = m->next)
1008 			;
1009 		*mp = m;
1010 		*pp = p+1;
1011 		break;
1012 	case '.':
1013 		*mp = cur;
1014 		*pp = p+1;
1015 		break;
1016 	case ',':
1017 		if (*mp == nil)
1018 			*mp = first;
1019 		*pp = p;
1020 		break;
1021 	}
1022 
1023 	if(*mp != nil && **pp == '.'){
1024 		(*pp)++;
1025 		if((*mp)->child == nil)
1026 			return "no sub parts";
1027 		return parseaddr(pp, (*mp)->child, (*mp)->child, (*mp)->child, mp);
1028 	}
1029 	if(**pp == '+' || **pp == '-' || **pp == '/' || **pp == '%')
1030 		return parseaddr(pp, first, *mp, *mp, mp);
1031 
1032 	return nil;
1033 }
1034 
1035 //
1036 //  search a message for a regular expression match
1037 //
1038 int
1039 rawsearch(Message *m, Reprog *prog)
1040 {
1041 	char buf[4096+1];
1042 	int i, fd, rv;
1043 	String *path;
1044 
1045 	path = extendpath(m->path, "raw");
1046 	fd = open(s_to_c(path), OREAD);
1047 	if(fd < 0)
1048 		return 0;
1049 
1050 	// march through raw message 4096 bytes at a time
1051 	// with a 128 byte overlap to chain the re search.
1052 	rv = 0;
1053 	for(;;){
1054 		i = read(fd, buf, sizeof(buf)-1);
1055 		if(i <= 0)
1056 			break;
1057 		buf[i] = 0;
1058 		if(regexec(prog, buf, nil, 0)){
1059 			rv = 1;
1060 			break;
1061 		}
1062 		if(i < sizeof(buf)-1)
1063 			break;
1064 		if(seek(fd, -128LL, 1) < 0)
1065 			break;
1066 	}
1067 
1068 	close(fd);
1069 	s_free(path);
1070 	return rv;
1071 }
1072 
1073 
1074 char*
1075 parsecmd(char *p, Cmd *cmd, Message *first, Message *cur)
1076 {
1077 	Reprog *prog;
1078 	Message *m, *s, *e, **l, *last;
1079 	char buf[256];
1080 	char *err;
1081 	int i, c;
1082 	char *q;
1083 	static char errbuf[Errlen];
1084 
1085 	cmd->delete = 0;
1086 	l = &cmd->msgs;
1087 	*l = nil;
1088 
1089 	// eat white space
1090 	while(*p == ' ')
1091 		p++;
1092 
1093 	// null command is a special case (advance and print)
1094 	if(*p == 0){
1095 		if(cur == &top){
1096 			// special case
1097 			m = first;
1098 		} else {
1099 			// walk to the next message even if we have to go up
1100 			m = cur->next;
1101 			while(m == nil && cur->parent != nil){
1102 				cur = cur->parent;
1103 				m = cur->next;
1104 			}
1105 		}
1106 		if(m == nil)
1107 			return "address";
1108 		*l = m;
1109 		m->cmd = nil;
1110 		cmd->an = 0;
1111 		cmd->f = pcmd;
1112 		return nil;
1113 	}
1114 
1115 	// global search ?
1116 	if(*p == 'g'){
1117 		p++;
1118 
1119 		// no search string means all messages
1120 		if(*p != '/' && *p != '%'){
1121 			for(m = first; m != nil; m = m->next){
1122 				*l = m;
1123 				l = &m->cmd;
1124 				*l = nil;
1125 			}
1126 		} else {
1127 			// mark all messages matching this search string
1128 			c = *p;
1129 			prog = parsesearch(&p);
1130 			if(prog == nil)
1131 				return "badly formed regular expression";
1132 			if(c == '%'){
1133 				for(m = first; m != nil; m = m->next){
1134 					if(rawsearch(m, prog)){
1135 						*l = m;
1136 						l = &m->cmd;
1137 						*l = nil;
1138 					}
1139 				}
1140 			} else {
1141 				for(m = first; m != nil; m = m->next){
1142 					snprintheader(buf, sizeof(buf), m);
1143 					if(regexec(prog, buf, nil, 0)){
1144 						*l = m;
1145 						l = &m->cmd;
1146 						*l = nil;
1147 					}
1148 				}
1149 			}
1150 			free(prog);
1151 		}
1152 	} else {
1153 
1154 		// parse an address
1155 		s = e = nil;
1156 		err = parseaddr(&p, first, cur, cur, &s);
1157 		if(err != nil)
1158 			return err;
1159 		if(*p == ','){
1160 			// this is an address range
1161 			if(s == &top)
1162 				s = first;
1163 			p++;
1164 			for(last = s; last != nil && last->next != nil; last = last->next)
1165 				;
1166 			err = parseaddr(&p, first, cur, last, &e);
1167 			if(err != nil)
1168 				return err;
1169 
1170 			// select all messages in the range
1171 			for(; s != nil; s = s->next){
1172 				*l = s;
1173 				l = &s->cmd;
1174 				*l = nil;
1175 				if(s == e)
1176 					break;
1177 			}
1178 			if(s == nil)
1179 				return "null address range";
1180 		} else {
1181 			// single address
1182 			if(s != &top){
1183 				*l = s;
1184 				s->cmd = nil;
1185 			}
1186 		}
1187 	}
1188 
1189 	// insert a space after '!'s and '|'s
1190 	for(q = p; *q; q++)
1191 		if(*q != '!' && *q != '|')
1192 			break;
1193 	if(q != p && *q != ' '){
1194 		memmove(q+1, q, strlen(q)+1);
1195 		*q = ' ';
1196 	}
1197 
1198 	cmd->an = getfields(p, cmd->av, nelem(cmd->av) - 1, 1, " \t\r\n");
1199 	if(cmd->an == 0 || *cmd->av[0] == 0)
1200 		cmd->f = pcmd;
1201 	else {
1202 		// hack to allow all messages to start with 'd'
1203 		if(*(cmd->av[0]) == 'd' && *(cmd->av[0]+1) != 0){
1204 			cmd->delete = 1;
1205 			cmd->av[0]++;
1206 		}
1207 
1208 		// search command table
1209 		for(i = 0; cmdtab[i].cmd != nil; i++)
1210 			if(strcmp(cmd->av[0], cmdtab[i].cmd) == 0)
1211 				break;
1212 		if(cmdtab[i].cmd == nil)
1213 			return "illegal command";
1214 		if(cmdtab[i].args == 0 && cmd->an > 1){
1215 			snprint(errbuf, sizeof(errbuf), "%s doesn't take an argument", cmdtab[i].cmd);
1216 			return errbuf;
1217 		}
1218 		cmd->f = cmdtab[i].f;
1219 	}
1220 	return nil;
1221 }
1222 
1223 // inefficient read from standard input
1224 char*
1225 readline(char *prompt, char *line, int len)
1226 {
1227 	char *p, *e;
1228 	int n;
1229 
1230 retry:
1231 	interrupted = 0;
1232 	Bprint(&out, "%s", prompt);
1233 	Bflush(&out);
1234 	e = line + len;
1235 	for(p = line; p < e; p++){
1236 		n = read(0, p, 1);
1237 		if(n < 0){
1238 			if(interrupted)
1239 				goto retry;
1240 			return nil;
1241 		}
1242 		if(n == 0)
1243 			return nil;
1244 		if(*p == '\n')
1245 			break;
1246 	}
1247 	*p = 0;
1248 	return line;
1249 }
1250 
1251 void
1252 messagecount(Message *m)
1253 {
1254 	int i;
1255 
1256 	i = 0;
1257 	for(; m != nil; m = m->next)
1258 		i++;
1259 	Bprint(&out, "%d message%s\n", i, plural(i));
1260 }
1261 
1262 Message*
1263 aichcmd(Message *m, int indent)
1264 {
1265 	char	hdr[256];
1266 
1267 	if(m == &top)
1268 		return nil;
1269 
1270 	snprintHeader(hdr, sizeof(hdr), indent, m);
1271 	Bprint(&out, "%s\n", hdr);
1272 	for(m = m->child; m != nil; m = m->next)
1273 		aichcmd(m, indent+1);
1274 	return nil;
1275 }
1276 
1277 Message*
1278 Hcmd(Cmd*, Message *m)
1279 {
1280 	if(m == &top)
1281 		return nil;
1282 	aichcmd(m, 0);
1283 	return nil;
1284 }
1285 
1286 Message*
1287 hcmd(Cmd*, Message *m)
1288 {
1289 	char	hdr[256];
1290 
1291 	if(m == &top)
1292 		return nil;
1293 
1294 	snprintheader(hdr, sizeof(hdr), m);
1295 	Bprint(&out, "%s\n", hdr);
1296 	return nil;
1297 }
1298 
1299 Message*
1300 bcmd(Cmd*, Message *m)
1301 {
1302 	int i;
1303 	Message *om = m;
1304 
1305 	if(m == &top)
1306 		m = top.child;
1307 	for(i = 0; i < 10 && m != nil; i++){
1308 		hcmd(nil, m);
1309 		om = m;
1310 		m = m->next;
1311 	}
1312 
1313 	return om;
1314 }
1315 
1316 Message*
1317 ncmd(Cmd*, Message *m)
1318 {
1319 	if(m == &top)
1320 		return m->child;
1321 	return m->next;
1322 }
1323 
1324 int
1325 printpart(String *s, char *part)
1326 {
1327 	char buf[4096];
1328 	int n, fd, tot;
1329 	String *path;
1330 
1331 	path = extendpath(s, part);
1332 	fd = open(s_to_c(path), OREAD);
1333 	s_free(path);
1334 	if(fd < 0){
1335 		fprint(2, "!message disappeared\n");
1336 		return 0;
1337 	}
1338 	tot = 0;
1339 	while((n = read(fd, buf, sizeof(buf))) > 0){
1340 		if(interrupted)
1341 			break;
1342 		if(Bwrite(&out, buf, n) <= 0)
1343 			break;
1344 		tot += n;
1345 	}
1346 	close(fd);
1347 	return tot;
1348 }
1349 
1350 int
1351 printhtml(Message *m)
1352 {
1353 	Cmd c;
1354 
1355 	c.an = 3;
1356 	c.av[1] = "/bin/htmlfmt";
1357 	c.av[2] = "-l 40 -cutf-8";
1358 	Bprint(&out, "!%s\n", c.av[1]);
1359 	Bflush(&out);
1360 	pipecmd(&c, m);
1361 	return 0;
1362 }
1363 
1364 Message*
1365 Pcmd(Cmd*, Message *m)
1366 {
1367 	if(m == &top)
1368 		return &top;
1369 	if(m->parent == &top)
1370 		printpart(m->path, "unixheader");
1371 	printpart(m->path, "raw");
1372 	return m;
1373 }
1374 
1375 void
1376 compress(char *p)
1377 {
1378 	char *np;
1379 	int last;
1380 
1381 	last = ' ';
1382 	for(np = p; *p; p++){
1383 		if(*p != ' ' || last != ' '){
1384 			last = *p;
1385 			*np++ = last;
1386 		}
1387 	}
1388 	*np = 0;
1389 }
1390 
1391 Message*
1392 pcmd(Cmd*, Message *m)
1393 {
1394 	Message *nm;
1395 	Ctype *cp;
1396 	String *s;
1397 	char buf[128];
1398 
1399 	if(m == &top)
1400 		return &top;
1401 	if(m->parent == &top)
1402 		printpart(m->path, "unixheader");
1403 	if(printpart(m->path, "header") > 0)
1404 		Bprint(&out, "\n");
1405 	cp = findctype(m);
1406 	if(cp->display){
1407 		if(strcmp(m->type, "text/html") == 0)
1408 			printhtml(m);
1409 		else
1410 			printpart(m->path, "body");
1411 	} else if(strcmp(m->type, "multipart/alternative") == 0){
1412 		for(nm = m->child; nm != nil; nm = nm->next){
1413 			cp = findctype(nm);
1414 			if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
1415 				break;
1416 		}
1417 		if(nm == nil)
1418 			for(nm = m->child; nm != nil; nm = nm->next){
1419 				cp = findctype(nm);
1420 				if(cp->display)
1421 					break;
1422 			}
1423 		if(nm != nil)
1424 			pcmd(nil, nm);
1425 		else
1426 			hcmd(nil, m);
1427 	} else if(strncmp(m->type, "multipart/", 10) == 0){
1428 		nm = m->child;
1429 		if(nm != nil){
1430 			// always print first part
1431 			pcmd(nil, nm);
1432 
1433 			for(nm = nm->next; nm != nil; nm = nm->next){
1434 				s = rooted(s_clone(nm->path));
1435 				cp = findctype(nm);
1436 				snprintHeader(buf, sizeof buf, -1, nm);
1437 				compress(buf);
1438 				if(strcmp(nm->disposition, "inline") == 0){
1439 					if(cp->ext != nil)
1440 						Bprint(&out, "\n--- %s %s/body.%s\n\n",
1441 							buf, s_to_c(s), cp->ext);
1442 					else
1443 						Bprint(&out, "\n--- %s %s/body\n\n",
1444 							buf, s_to_c(s));
1445 					pcmd(nil, nm);
1446 				} else {
1447 					if(cp->ext != nil)
1448 						Bprint(&out, "\n!--- %s %s/body.%s\n",
1449 							buf, s_to_c(s), cp->ext);
1450 					else
1451 						Bprint(&out, "\n!--- %s %s/body\n",
1452 							buf, s_to_c(s));
1453 				}
1454 				s_free(s);
1455 			}
1456 		} else {
1457 			hcmd(nil, m);
1458 		}
1459 	} else if(strcmp(m->type, "message/rfc822") == 0){
1460 		pcmd(nil, m->child);
1461 	} else if(plumb(m, cp) >= 0)
1462 		Bprint(&out, "\n!--- using plumber to display message of type %s\n", m->type);
1463 	else
1464 		Bprint(&out, "\n!--- cannot display messages of type %s\n", m->type);
1465 
1466 	return m;
1467 }
1468 
1469 void
1470 printpartindented(String *s, char *part, char *indent)
1471 {
1472 	char *p;
1473 	String *path;
1474 	Biobuf *b;
1475 
1476 	path = extendpath(s, part);
1477 	b = Bopen(s_to_c(path), OREAD);
1478 	s_free(path);
1479 	if(b == nil){
1480 		fprint(2, "!message disappeared\n");
1481 		return;
1482 	}
1483 	while((p = Brdline(b, '\n')) != nil){
1484 		if(interrupted)
1485 			break;
1486 		p[Blinelen(b)-1] = 0;
1487 		if(Bprint(&out, "%s%s\n", indent, p) < 0)
1488 			break;
1489 	}
1490 	Bprint(&out, "\n");
1491 	Bterm(b);
1492 }
1493 
1494 Message*
1495 quotecmd(Cmd*, Message *m)
1496 {
1497 	Message *nm;
1498 	Ctype *cp;
1499 
1500 	if(m == &top)
1501 		return &top;
1502 	Bprint(&out, "\n");
1503 	if(m->from != nil && *m->from)
1504 		Bprint(&out, "On %s, %s wrote:\n", m->date, m->from);
1505 	cp = findctype(m);
1506 	if(cp->display){
1507 		printpartindented(m->path, "body", "> ");
1508 	} else if(strcmp(m->type, "multipart/alternative") == 0){
1509 		for(nm = m->child; nm != nil; nm = nm->next){
1510 			cp = findctype(nm);
1511 			if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
1512 				break;
1513 		}
1514 		if(nm == nil)
1515 			for(nm = m->child; nm != nil; nm = nm->next){
1516 				cp = findctype(nm);
1517 				if(cp->display)
1518 					break;
1519 			}
1520 		if(nm != nil)
1521 			quotecmd(nil, nm);
1522 	} else if(strncmp(m->type, "multipart/", 10) == 0){
1523 		nm = m->child;
1524 		if(nm != nil){
1525 			cp = findctype(nm);
1526 			if(cp->display || strncmp(m->type, "multipart/", 10) == 0)
1527 				quotecmd(nil, nm);
1528 		}
1529 	}
1530 	return m;
1531 }
1532 
1533 // really delete messages
1534 Message*
1535 flushdeleted(Message *cur)
1536 {
1537 	Message *m, **l;
1538 	char buf[1024], *p, *e, *msg;
1539 	int deld, n, fd;
1540 	int i;
1541 
1542 	doflush = 0;
1543 	deld = 0;
1544 
1545 	fd = open("/mail/fs/ctl", ORDWR);
1546 	if(fd < 0){
1547 		fprint(2, "!can't delete mail, opening /mail/fs/ctl: %r\n");
1548 		exitfs(0);
1549 	}
1550 	e = &buf[sizeof(buf)];
1551 	p = seprint(buf, e, "delete %s", mbname);
1552 	n = 0;
1553 	for(l = &top.child; *l != nil;){
1554 		m = *l;
1555 		if(!m->deleted){
1556 			l = &(*l)->next;
1557 			continue;
1558 		}
1559 
1560 		// don't return a pointer to a deleted message
1561 		if(m == cur)
1562 			cur = m->next;
1563 
1564 		deld++;
1565 		msg = strrchr(s_to_c(m->path), '/');
1566 		if(msg == nil)
1567 			msg = s_to_c(m->path);
1568 		else
1569 			msg++;
1570 		if(e-p < 10){
1571 			write(fd, buf, p-buf);
1572 			n = 0;
1573 			p = seprint(buf, e, "delete %s", mbname);
1574 		}
1575 		p = seprint(p, e, " %s", msg);
1576 		n++;
1577 
1578 		// unchain and free
1579 		*l = m->next;
1580 		if(m->next)
1581 			m->next->prev = m->prev;
1582 		freemessage(m);
1583 	}
1584 	if(n)
1585 		write(fd, buf, p-buf);
1586 
1587 	close(fd);
1588 
1589 	if(deld)
1590 		Bprint(&out, "!%d message%s deleted\n", deld, plural(deld));
1591 
1592 	// renumber
1593 	i = 1;
1594 	for(m = top.child; m != nil; m = m->next)
1595 		m->id = natural ? m->fileno : i++;
1596 
1597 	// if we're out of messages, go back to first
1598 	// if no first, return the fake first
1599 	if(cur == nil){
1600 		if(top.child)
1601 			return top.child;
1602 		else
1603 			return &top;
1604 	}
1605 	return cur;
1606 }
1607 
1608 Message*
1609 qcmd(Cmd*, Message*)
1610 {
1611 	flushdeleted(nil);
1612 
1613 	if(didopen)
1614 		closemb();
1615 	Bflush(&out);
1616 
1617 	exitfs(0);
1618 	return nil;	// not reached
1619 }
1620 
1621 Message*
1622 ycmd(Cmd*, Message *m)
1623 {
1624 	doflush = 1;
1625 
1626 	return icmd(nil, m);
1627 }
1628 
1629 Message*
1630 xcmd(Cmd*, Message*)
1631 {
1632 	exitfs(0);
1633 	return nil;	// not reached
1634 }
1635 
1636 Message*
1637 eqcmd(Cmd*, Message *m)
1638 {
1639 	if(m == &top)
1640 		Bprint(&out, "0\n");
1641 	else
1642 		Bprint(&out, "%d\n", m->id);
1643 	return nil;
1644 }
1645 
1646 Message*
1647 dcmd(Cmd*, Message *m)
1648 {
1649 	if(m == &top){
1650 		Bprint(&out, "!address\n");
1651 		return nil;
1652 	}
1653 	while(m->parent != &top)
1654 		m = m->parent;
1655 	m->deleted = 1;
1656 	return m;
1657 }
1658 
1659 Message*
1660 ucmd(Cmd*, Message *m)
1661 {
1662 	if(m == &top)
1663 		return nil;
1664 	while(m->parent != &top)
1665 		m = m->parent;
1666 	if(m->deleted < 0)
1667 		Bprint(&out, "!can't undelete, already flushed\n");
1668 	m->deleted = 0;
1669 	return m;
1670 }
1671 
1672 
1673 Message*
1674 icmd(Cmd*, Message *m)
1675 {
1676 	int n;
1677 
1678 	n = dir2message(&top, reverse);
1679 	if(n > 0)
1680 		Bprint(&out, "%d new message%s\n", n, plural(n));
1681 	return m;
1682 }
1683 
1684 Message*
1685 helpcmd(Cmd*, Message *m)
1686 {
1687 	int i;
1688 
1689 	Bprint(&out, "Commands are of the form [<range>] <command> [args]\n");
1690 	Bprint(&out, "<range> := <addr> | <addr>','<addr>| 'g'<search>\n");
1691 	Bprint(&out, "<addr> := '.' | '$' | '^' | <number> | <search> | <addr>'+'<addr> | <addr>'-'<addr>\n");
1692 	Bprint(&out, "<search> := '/'<regexp>'/' | '?'<regexp>'?' | '%%'<regexp>'%%'\n");
1693 	Bprint(&out, "<command> :=\n");
1694 	for(i = 0; cmdtab[i].cmd != nil; i++)
1695 		Bprint(&out, "%s\n", cmdtab[i].help);
1696 	return m;
1697 }
1698 
1699 int
1700 tomailer(char **av)
1701 {
1702 	Waitmsg *w;
1703 	int pid, i;
1704 
1705 	// start the mailer and get out of the way
1706 	switch(pid = fork()){
1707 	case -1:
1708 		fprint(2, "can't fork: %r\n");
1709 		return -1;
1710 	case 0:
1711 		Bprint(&out, "!/bin/upas/marshal");
1712 		for(i = 1; av[i]; i++)
1713 			Bprint(&out, " %q", av[i]);
1714 		Bprint(&out, "\n");
1715 		Bflush(&out);
1716 		av[0] = "marshal";
1717 		chdir(wd);
1718 		exec("/bin/upas/marshal", av);
1719 		fprint(2, "couldn't exec /bin/upas/marshal\n");
1720 		exits(0);
1721 	default:
1722 		w = wait();
1723 		if(w == nil){
1724 			if(interrupted)
1725 				postnote(PNPROC, pid, "die");
1726 			waitpid();
1727 			return -1;
1728 		}
1729 		if(w->msg[0]){
1730 			fprint(2, "mailer failed: %s\n", w->msg);
1731 			free(w);
1732 			return -1;
1733 		}
1734 		free(w);
1735 		Bprint(&out, "!\n");
1736 		break;
1737 	}
1738 	return 0;
1739 }
1740 
1741 //
1742 // like tokenize but obey "" quoting
1743 //
1744 int
1745 tokenize822(char *str, char **args, int max)
1746 {
1747 	int na;
1748 	int intok = 0, inquote = 0;
1749 
1750 	if(max <= 0)
1751 		return 0;
1752 	for(na=0; ;str++)
1753 		switch(*str) {
1754 		case ' ':
1755 		case '\t':
1756 			if(inquote)
1757 				goto Default;
1758 			/* fall through */
1759 		case '\n':
1760 			*str = 0;
1761 			if(!intok)
1762 				continue;
1763 			intok = 0;
1764 			if(na < max)
1765 				continue;
1766 			/* fall through */
1767 		case 0:
1768 			return na;
1769 		case '"':
1770 			inquote ^= 1;
1771 			/* fall through */
1772 		Default:
1773 		default:
1774 			if(intok)
1775 				continue;
1776 			args[na++] = str;
1777 			intok = 1;
1778 		}
1779 }
1780 
1781 /* return reply-to address & set *nmp to corresponding Message */
1782 static char *
1783 getreplyto(Message *m, Message **nmp)
1784 {
1785 	Message *nm;
1786 
1787 	for(nm = m; nm != &top; nm = nm->parent)
1788  		if(*nm->replyto != 0)
1789 			break;
1790 	*nmp = nm;
1791 	return nm? nm->replyto: nil;
1792 }
1793 
1794 Message*
1795 rcmd(Cmd *c, Message *m)
1796 {
1797 	char *addr;
1798 	char *av[128];
1799 	int i, ai = 1;
1800 	String *from, *rpath, *path = nil, *subject = nil;
1801 	Message *nm;
1802 
1803 	if(m == &top){
1804 		Bprint(&out, "!address\n");
1805 		return nil;
1806 	}
1807 
1808 	addr = getreplyto(m, &nm);
1809 	if(addr == nil){
1810 		Bprint(&out, "!no reply address\n");
1811 		return nil;
1812 	}
1813 	if(nm == &top){
1814 		print("!noone to reply to\n");
1815 		return nil;
1816 	}
1817 
1818 	for(nm = m; nm != &top; nm = nm->parent){
1819 		if(*nm->subject){
1820 			av[ai++] = "-s";
1821 			subject = addrecolon(nm->subject);
1822 			av[ai++] = s_to_c(subject);
1823 			break;
1824 		}
1825 	}
1826 
1827 	av[ai++] = "-R";
1828 	rpath = rooted(s_clone(m->path));
1829 	av[ai++] = s_to_c(rpath);
1830 
1831 	if(strchr(c->av[0], 'f') != nil){
1832 		fcmd(c, m);
1833 		av[ai++] = "-F";
1834 	}
1835 
1836 	if(strchr(c->av[0], 'R') != nil){
1837 		av[ai++] = "-t";
1838 		av[ai++] = "message/rfc822";
1839 		av[ai++] = "-A";
1840 		path = rooted(extendpath(m->path, "raw"));
1841 		av[ai++] = s_to_c(path);
1842 	}
1843 
1844 	for(i = 1; i < c->an && ai < nelem(av)-1; i++)
1845 		av[ai++] = c->av[i];
1846 	from = s_copy(addr);
1847 	ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
1848 	av[ai] = 0;
1849 	if(tomailer(av) < 0)
1850 		m = nil;
1851 	s_free(path);
1852 	s_free(rpath);
1853 	s_free(subject);
1854 	s_free(from);
1855 	return m;
1856 }
1857 
1858 Message*
1859 mcmd(Cmd *c, Message *m)
1860 {
1861 	char **av;
1862 	int i, ai;
1863 	String *path;
1864 
1865 	if(m == &top){
1866 		Bprint(&out, "!address\n");
1867 		return nil;
1868 	}
1869 
1870 	if(c->an < 2){
1871 		fprint(2, "!usage: M list-of addresses\n");
1872 		return nil;
1873 	}
1874 
1875 	ai = 1;
1876 	av = malloc(sizeof(char*)*(c->an + 8));
1877 
1878 	av[ai++] = "-t";
1879 	if(m->parent == &top)
1880 		av[ai++] = "message/rfc822";
1881 	else
1882 		av[ai++] = "mime";
1883 
1884 	av[ai++] = "-A";
1885 	path = rooted(extendpath(m->path, "raw"));
1886 	av[ai++] = s_to_c(path);
1887 
1888 	if(strchr(c->av[0], 'M') == nil)
1889 		av[ai++] = "-n";
1890 
1891 	for(i = 1; i < c->an; i++)
1892 		av[ai++] = c->av[i];
1893 	av[ai] = 0;
1894 
1895 	if(tomailer(av) < 0)
1896 		m = nil;
1897 	if(path != nil)
1898 		s_free(path);
1899 	free(av);
1900 	return m;
1901 }
1902 
1903 Message*
1904 acmd(Cmd *c, Message *m)
1905 {
1906 	char *av[128];
1907 	int i, ai = 1;
1908 	String *from, *rpath, *path = nil, *subject = nil;
1909 	String *to, *cc;
1910 
1911 	if(m == &top){
1912 		Bprint(&out, "!address\n");
1913 		return nil;
1914 	}
1915 
1916 	if(*m->subject){
1917 		av[ai++] = "-s";
1918 		subject = addrecolon(m->subject);
1919 		av[ai++] = s_to_c(subject);
1920 	}
1921 
1922 	av[ai++] = "-R";
1923 	rpath = rooted(s_clone(m->path));
1924 	av[ai++] = s_to_c(rpath);
1925 
1926 	if(strchr(c->av[0], 'A') != nil){
1927 		av[ai++] = "-t";
1928 		av[ai++] = "message/rfc822";
1929 		av[ai++] = "-A";
1930 		path = rooted(extendpath(m->path, "raw"));
1931 		av[ai++] = s_to_c(path);
1932 	}
1933 
1934 	for(i = 1; i < c->an && ai < nelem(av)-1; i++)
1935 		av[ai++] = c->av[i];
1936 	from = s_copy(m->from);
1937 	ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
1938 	to = s_copy(m->to);
1939 	ai += tokenize822(s_to_c(to), &av[ai], nelem(av) - ai);
1940 	cc = s_copy(m->cc);
1941 	ai += tokenize822(s_to_c(cc), &av[ai], nelem(av) - ai);
1942 	av[ai] = 0;
1943 	if(tomailer(av) < 0)
1944 		m = nil;
1945 	s_free(path);
1946 	s_free(rpath);
1947 	s_free(subject);
1948 	s_free(from);
1949 	s_free(to);
1950 	s_free(cc);
1951 	return m;
1952 }
1953 
1954 String *
1955 relpath(char *path, String *to)
1956 {
1957 	if (*path=='/' || strncmp(path, "./", 2) == 0
1958 			      || strncmp(path, "../", 3) == 0) {
1959 		to = s_append(to, path);
1960 	} else if(mbpath) {
1961 		to = s_append(to, s_to_c(mbpath));
1962 		to->ptr = strrchr(to->base, '/')+1;
1963 		s_append(to, path);
1964 	}
1965 	return to;
1966 }
1967 
1968 int
1969 appendtofile(Message *m, char *part, char *base, int mbox)
1970 {
1971 	String *file, *h;
1972 	int in, out, rv;
1973 
1974 	file = extendpath(m->path, part);
1975 	in = open(s_to_c(file), OREAD);
1976 	if(in < 0){
1977 		fprint(2, "!message disappeared\n");
1978 		return -1;
1979 	}
1980 
1981 	s_reset(file);
1982 
1983 	relpath(base, file);
1984 	if(sysisdir(s_to_c(file))){
1985 		s_append(file, "/");
1986 		if(m->filename && strchr(m->filename, '/') == nil)
1987 			s_append(file, m->filename);
1988 		else {
1989 			s_append(file, "att.XXXXXXXXXXX");
1990 			mktemp(s_to_c(file));
1991 		}
1992 	}
1993 	if(mbox)
1994 		out = open(s_to_c(file), OWRITE);
1995 	else
1996 		out = open(s_to_c(file), OWRITE|OTRUNC);
1997 	if(out < 0){
1998 		out = create(s_to_c(file), OWRITE, 0666);
1999 		if(out < 0){
2000 			fprint(2, "!can't open %s: %r\n", s_to_c(file));
2001 			close(in);
2002 			s_free(file);
2003 			return -1;
2004 		}
2005 	}
2006 	if(mbox)
2007 		seek(out, 0, 2);
2008 
2009 	// put on a 'From ' line
2010 	if(mbox){
2011 		while(m->parent != &top)
2012 			m = m->parent;
2013 		h = file2string(m->path, "unixheader");
2014 		fprint(out, "%s", s_to_c(h));
2015 		s_free(h);
2016 	}
2017 
2018 	// copy the message escaping what we have to ad adding newlines if we have to
2019 	if(mbox)
2020 		rv = appendfiletombox(in, out);
2021 	else
2022 		rv = appendfiletofile(in, out);
2023 
2024 	close(in);
2025 	close(out);
2026 
2027 	if(rv >= 0)
2028 		print("!saved in %s\n", s_to_c(file));
2029 	s_free(file);
2030 	return rv;
2031 }
2032 
2033 Message*
2034 scmd(Cmd *c, Message *m)
2035 {
2036 	char *file;
2037 
2038 	if(m == &top){
2039 		Bprint(&out, "!address\n");
2040 		return nil;
2041 	}
2042 
2043 	switch(c->an){
2044 	case 1:
2045 		file = "stored";
2046 		break;
2047 	case 2:
2048 		file = c->av[1];
2049 		break;
2050 	default:
2051 		fprint(2, "!usage: s filename\n");
2052 		return nil;
2053 	}
2054 
2055 	if(appendtofile(m, "raw", file, 1) < 0)
2056 		return nil;
2057 
2058 	m->stored = 1;
2059 	return m;
2060 }
2061 
2062 Message*
2063 wcmd(Cmd *c, Message *m)
2064 {
2065 	char *file;
2066 
2067 	if(m == &top){
2068 		Bprint(&out, "!address\n");
2069 		return nil;
2070 	}
2071 
2072 	switch(c->an){
2073 	case 2:
2074 		file = c->av[1];
2075 		break;
2076 	case 1:
2077 		if(*m->filename == 0){
2078 			fprint(2, "!usage: w filename\n");
2079 			return nil;
2080 		}
2081 		file = strrchr(m->filename, '/');
2082 		if(file != nil)
2083 			file++;
2084 		else
2085 			file = m->filename;
2086 		break;
2087 	default:
2088 		fprint(2, "!usage: w filename\n");
2089 		return nil;
2090 	}
2091 
2092 	if(appendtofile(m, "body", file, 0) < 0)
2093 		return nil;
2094 	m->stored = 1;
2095 	return m;
2096 }
2097 
2098 char *specialfile[] =
2099 {
2100 	"pipeto",
2101 	"pipefrom",
2102 	"L.mbox",
2103 	"forward",
2104 	"names"
2105 };
2106 
2107 // return 1 if this is a special file
2108 static int
2109 special(String *s)
2110 {
2111 	char *p;
2112 	int i;
2113 
2114 	p = strrchr(s_to_c(s), '/');
2115 	if(p == nil)
2116 		p = s_to_c(s);
2117 	else
2118 		p++;
2119 	for(i = 0; i < nelem(specialfile); i++)
2120 		if(strcmp(p, specialfile[i]) == 0)
2121 			return 1;
2122 	return 0;
2123 }
2124 
2125 // open the folder using the recipients account name
2126 static String*
2127 foldername(char *rcvr)
2128 {
2129 	char *p;
2130 	int c;
2131 	String *file;
2132 	Dir *d;
2133 	int scarey;
2134 
2135 	file = s_new();
2136 	mboxpath("f", user, file, 0);
2137 	d = dirstat(s_to_c(file));
2138 
2139 	// if $mail/f exists, store there, otherwise in $mail
2140 	s_restart(file);
2141 	if(d && d->qid.type == QTDIR){
2142 		scarey = 0;
2143 		s_append(file, "f/");
2144 	} else {
2145 		scarey = 1;
2146 	}
2147 	free(d);
2148 
2149 	p = strrchr(rcvr, '!');
2150 	if(p != nil)
2151 		rcvr = p+1;
2152 
2153 	while(*rcvr && *rcvr != '@'){
2154 		c = *rcvr++;
2155 		if(c == '/')
2156 			c = '_';
2157 		s_putc(file, c);
2158 	}
2159 	s_terminate(file);
2160 
2161 	if(scarey && special(file)){
2162 		fprint(2, "!won't overwrite %s\n", s_to_c(file));
2163 		s_free(file);
2164 		return nil;
2165 	}
2166 
2167 	return file;
2168 }
2169 
2170 Message*
2171 fcmd(Cmd *c, Message *m)
2172 {
2173 	String *folder;
2174 
2175 	if(c->an > 1){
2176 		fprint(2, "!usage: f takes no arguments\n");
2177 		return nil;
2178 	}
2179 
2180 	if(m == &top){
2181 		Bprint(&out, "!address\n");
2182 		return nil;
2183 	}
2184 
2185 	folder = foldername(m->from);
2186 	if(folder == nil)
2187 		return nil;
2188 
2189 	if(appendtofile(m, "raw", s_to_c(folder), 1) < 0){
2190 		s_free(folder);
2191 		return nil;
2192 	}
2193 	s_free(folder);
2194 
2195 	m->stored = 1;
2196 	return m;
2197 }
2198 
2199 void
2200 system(char *cmd, char **av, int in)
2201 {
2202 	int pid;
2203 
2204 	switch(pid=fork()){
2205 	case -1:
2206 		return;
2207 	case 0:
2208 		if(in >= 0){
2209 			close(0);
2210 			dup(in, 0);
2211 			close(in);
2212 		}
2213 		if(wd[0] != 0)
2214 			chdir(wd);
2215 		exec(cmd, av);
2216 		fprint(2, "!couldn't exec %s\n", cmd);
2217 		exits(0);
2218 	default:
2219 		if(in >= 0)
2220 			close(in);
2221 		while(waitpid() < 0){
2222 			if(!interrupted)
2223 				break;
2224 			postnote(PNPROC, pid, "die");
2225 			continue;
2226 		}
2227 		break;
2228 	}
2229 }
2230 
2231 Message*
2232 bangcmd(Cmd *c, Message *m)
2233 {
2234 	char cmd[4*1024];
2235 	char *p, *e;
2236 	char *av[4];
2237 	int i;
2238 
2239 	cmd[0] = 0;
2240 	p = cmd;
2241 	e = cmd+sizeof(cmd);
2242 	for(i = 1; i < c->an; i++)
2243 		p = seprint(p, e, "%s ", c->av[i]);
2244 	av[0] = "rc";
2245 	av[1] = "-c";
2246 	av[2] = cmd;
2247 	av[3] = 0;
2248 	system("/bin/rc", av, -1);
2249 	Bprint(&out, "!\n");
2250 	return m;
2251 }
2252 
2253 Message*
2254 xpipecmd(Cmd *c, Message *m, char *part)
2255 {
2256 	char cmd[128];
2257 	char *p, *e;
2258 	char *av[4];
2259 	String *path;
2260 	int i, fd;
2261 
2262 	if(c->an < 2){
2263 		Bprint(&out, "!usage: | cmd\n");
2264 		return nil;
2265 	}
2266 
2267 	if(m == &top){
2268 		Bprint(&out, "!address\n");
2269 		return nil;
2270 	}
2271 
2272 	path = extendpath(m->path, part);
2273 	fd = open(s_to_c(path), OREAD);
2274 	s_free(path);
2275 	if(fd < 0){	// compatibility with older upas/fs
2276 		path = extendpath(m->path, "raw");
2277 		fd = open(s_to_c(path), OREAD);
2278 		s_free(path);
2279 	}
2280 	if(fd < 0){
2281 		fprint(2, "!message disappeared\n");
2282 		return nil;
2283 	}
2284 
2285 	p = cmd;
2286 	e = cmd+sizeof(cmd);
2287 	cmd[0] = 0;
2288 	for(i = 1; i < c->an; i++)
2289 		p = seprint(p, e, "%s ", c->av[i]);
2290 	av[0] = "rc";
2291 	av[1] = "-c";
2292 	av[2] = cmd;
2293 	av[3] = 0;
2294 	system("/bin/rc", av, fd);	/* system closes fd */
2295 	Bprint(&out, "!\n");
2296 	return m;
2297 }
2298 
2299 Message*
2300 pipecmd(Cmd *c, Message *m)
2301 {
2302 	return xpipecmd(c, m, "body");
2303 }
2304 
2305 Message*
2306 rpipecmd(Cmd *c, Message *m)
2307 {
2308 	return xpipecmd(c, m, "rawunix");
2309 }
2310 
2311 void
2312 closemb(void)
2313 {
2314 	int fd;
2315 
2316 	fd = open("/mail/fs/ctl", ORDWR);
2317 	if(fd < 0)
2318 		sysfatal("can't open /mail/fs/ctl: %r");
2319 
2320 	// close current mailbox
2321 	if(*mbname && strcmp(mbname, "mbox") != 0)
2322 		fprint(fd, "close %s", mbname);
2323 
2324 	close(fd);
2325 }
2326 
2327 int
2328 switchmb(char *file, char *singleton)
2329 {
2330 	char *p;
2331 	int n, fd;
2332 	String *path;
2333 	char buf[256];
2334 
2335 	// if the user didn't say anything and there
2336 	// is an mbox mounted already, use that one
2337 	// so that the upas/fs -fdefault default is honored.
2338 	if(file
2339 	|| (singleton && access(singleton, 0)<0)
2340 	|| (!singleton && access("/mail/fs/mbox", 0)<0)){
2341 		if(file == nil)
2342 			file = "mbox";
2343 
2344 		// close current mailbox
2345 		closemb();
2346 		didopen = 1;
2347 
2348 		fd = open("/mail/fs/ctl", ORDWR);
2349 		if(fd < 0)
2350 			sysfatal("can't open /mail/fs/ctl: %r");
2351 
2352 		path = s_new();
2353 
2354 		// get an absolute path to the mail box
2355 		if(strncmp(file, "./", 2) == 0){
2356 			// resolve path here since upas/fs doesn't know
2357 			// our working directory
2358 			if(getwd(buf, sizeof(buf)-strlen(file)) == nil){
2359 				fprint(2, "!can't get working directory: %s\n", buf);
2360 				return -1;
2361 			}
2362 			s_append(path, buf);
2363 			s_append(path, file+1);
2364 		} else {
2365 			mboxpath(file, user, path, 0);
2366 		}
2367 
2368 		// make up a handle to use when talking to fs
2369 		p = strrchr(file, '/');
2370 		if(p == nil){
2371 			// if its in the mailbox directory, just use the name
2372 			strncpy(mbname, file, sizeof(mbname));
2373 			mbname[sizeof(mbname)-1] = 0;
2374 		} else {
2375 			// make up a mailbox name
2376 			p = strrchr(s_to_c(path), '/');
2377 			p++;
2378 			if(*p == 0){
2379 				fprint(2, "!bad mbox name");
2380 				return -1;
2381 			}
2382 			strncpy(mbname, p, sizeof(mbname));
2383 			mbname[sizeof(mbname)-1] = 0;
2384 			n = strlen(mbname);
2385 			if(n > Elemlen-12)
2386 				n = Elemlen-12;
2387 			sprint(mbname+n, "%ld", time(0));
2388 		}
2389 
2390 		if(fprint(fd, "open %s %s", s_to_c(path), mbname) < 0){
2391 			fprint(2, "!can't 'open %s %s': %r\n", file, mbname);
2392 			s_free(path);
2393 			return -1;
2394 		}
2395 		close(fd);
2396 	}else
2397 	if (singleton && access(singleton, 0)==0
2398 	    && strncmp(singleton, "/mail/fs/", 9) == 0){
2399 		if ((p = strchr(singleton +10, '/')) == nil){
2400 			fprint(2, "!bad mbox name");
2401 			return -1;
2402 		}
2403 		n = p-(singleton+9);
2404 		strncpy(mbname, singleton+9, n);
2405 		mbname[n+1] = 0;
2406 		path = s_reset(nil);
2407 		mboxpath(mbname, user, path, 0);
2408 	}else{
2409 		path = s_reset(nil);
2410 		mboxpath("mbox", user, path, 0);
2411 		strcpy(mbname, "mbox");
2412 	}
2413 
2414 	snprint(root, sizeof root, "/mail/fs/%s", mbname);
2415 	if(getwd(wd, sizeof(wd)) == 0)
2416 		wd[0] = 0;
2417 	if(singleton == nil && chdir(root) >= 0)
2418 		strcpy(root, ".");
2419 	rootlen = strlen(root);
2420 
2421 	if(mbpath != nil)
2422 		s_free(mbpath);
2423 	mbpath = path;
2424 	return 0;
2425 }
2426 
2427 // like tokenize but for into lines
2428 int
2429 lineize(char *s, char **f, int n)
2430 {
2431 	int i;
2432 
2433 	for(i = 0; *s && i < n; i++){
2434 		f[i] = s;
2435 		s = strchr(s, '\n');
2436 		if(s == nil)
2437 			break;
2438 		*s++ = 0;
2439 	}
2440 	return i;
2441 }
2442 
2443 
2444 
2445 String*
2446 rooted(String *s)
2447 {
2448 	static char buf[256];
2449 
2450 	if(strcmp(root, ".") != 0)
2451 		return s;
2452 	snprint(buf, sizeof(buf), "/mail/fs/%s/%s", mbname, s_to_c(s));
2453 	s_free(s);
2454 	return s_copy(buf);
2455 }
2456 
2457 int
2458 plumb(Message *m, Ctype *cp)
2459 {
2460 	String *s;
2461 	Plumbmsg *pm;
2462 	static int fd = -2;
2463 
2464 	if(cp->plumbdest == nil)
2465 		return -1;
2466 
2467 	if(fd < -1)
2468 		fd = plumbopen("send", OWRITE);
2469 	if(fd < 0)
2470 		return -1;
2471 
2472 	pm = mallocz(sizeof(Plumbmsg), 1);
2473 	pm->src = strdup("mail");
2474 	if(*cp->plumbdest)
2475 		pm->dst = strdup(cp->plumbdest);
2476 	pm->wdir = nil;
2477 	pm->type = strdup("text");
2478 	pm->ndata = -1;
2479 	s = rooted(extendpath(m->path, "body"));
2480 	if(cp->ext != nil){
2481 		s_append(s, ".");
2482 		s_append(s, cp->ext);
2483 	}
2484 	pm->data = strdup(s_to_c(s));
2485 	s_free(s);
2486 	plumbsend(fd, pm);
2487 	plumbfree(pm);
2488 	return 0;
2489 }
2490 
2491 void
2492 regerror(char*)
2493 {
2494 }
2495 
2496 String*
2497 addrecolon(char *s)
2498 {
2499 	String *str;
2500 
2501 	if(cistrncmp(s, "re:", 3) != 0){
2502 		str = s_copy("Re: ");
2503 		s_append(str, s);
2504 	} else
2505 		str = s_copy(s);
2506 	return str;
2507 }
2508 
2509 void
2510 exitfs(char *rv)
2511 {
2512 	if(startedfs)
2513 		unmount(nil, "/mail/fs");
2514 	chdir("/sys/src/cmd/upas/ned");		/* for profiling? */
2515 	exits(rv);
2516 }
2517