xref: /plan9-contrib/sys/src/cmd/upas/ned/nedmail.c (revision 34c2901791623ea03308d4cc8cd056b841394d48)
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 	char *err;
216 	int n, cflag;
217 	char *av[4];
218 	String *prompt;
219 	char *file, *singleton;
220 
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 char*
881 parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp)
882 {
883 	int n;
884 	Message *m;
885 	char *p;
886 	Reprog *prog;
887 	int c, sign;
888 	char buf[256];
889 
890 	*mp = nil;
891 	p = *pp;
892 
893 	if(*p == '+'){
894 		sign = 1;
895 		p++;
896 		*pp = p;
897 	} else if(*p == '-'){
898 		sign = -1;
899 		p++;
900 		*pp = p;
901 	} else
902 		sign = 0;
903 
904 	switch(*p){
905 	default:
906 		if(sign){
907 			n = 1;
908 			goto number;
909 		}
910 		*mp = unspec;
911 		break;
912 	case '0': case '1': case '2': case '3': case '4':
913 	case '5': case '6': case '7': case '8': case '9':
914 		n = strtoul(p, pp, 10);
915 		if(n == 0){
916 			if(sign)
917 				*mp = cur;
918 			else
919 				*mp = &top;
920 			break;
921 		}
922 	number:
923 		m = nil;
924 		switch(sign){
925 		case 0:
926 			for(m = first; m != nil; m = m->next)
927 				if(m->id == n)
928 					break;
929 			break;
930 		case -1:
931 			if(cur != &top)
932 				for(m = cur; m != nil && n > 0; n--)
933 					m = m->prev;
934 			break;
935 		case 1:
936 			if(cur == &top){
937 				n--;
938 				cur = first;
939 			}
940 			for(m = cur; m != nil && n > 0; n--)
941 				m = m->next;
942 			break;
943 		}
944 		if(m == nil)
945 			return "address";
946 		*mp = m;
947 		break;
948 	case '%':
949 	case '/':
950 	case '?':
951 		c = *p;
952 		prog = parsesearch(pp);
953 		if(prog == nil)
954 			return "badly formed regular expression";
955 		m = nil;
956 		switch(c){
957 		case '%':
958 			for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
959 				if(rawsearch(m, prog))
960 					break;
961 			}
962 			break;
963 		case '/':
964 			for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
965 				snprintheader(buf, sizeof(buf), m);
966 				if(regexec(prog, buf, nil, 0))
967 					break;
968 			}
969 			break;
970 		case '?':
971 			for(m = cur == &top ? nil : cur->prev; m != nil; m = m->prev){
972 				snprintheader(buf, sizeof(buf), m);
973 				if(regexec(prog, buf, nil, 0))
974 					break;
975 			}
976 			break;
977 		}
978 		if(m == nil)
979 			return "search";
980 		*mp = m;
981 		free(prog);
982 		break;
983 	case '$':
984 		for(m = first; m != nil && m->next != nil; m = m->next)
985 			;
986 		*mp = m;
987 		*pp = p+1;
988 		break;
989 	case '.':
990 		*mp = cur;
991 		*pp = p+1;
992 		break;
993 	case ',':
994 		*mp = first;
995 		*pp = p;
996 		break;
997 	}
998 
999 	if(*mp != nil && **pp == '.'){
1000 		(*pp)++;
1001 		if((*mp)->child == nil)
1002 			return "no sub parts";
1003 		return parseaddr(pp, (*mp)->child, (*mp)->child, (*mp)->child, mp);
1004 	}
1005 	if(**pp == '+' || **pp == '-' || **pp == '/' || **pp == '%')
1006 		return parseaddr(pp, first, *mp, *mp, mp);
1007 
1008 	return nil;
1009 }
1010 
1011 //
1012 //  search a message for a regular expression match
1013 //
1014 int
1015 rawsearch(Message *m, Reprog *prog)
1016 {
1017 	char buf[4096+1];
1018 	int i, fd, rv;
1019 	String *path;
1020 
1021 	path = extendpath(m->path, "raw");
1022 	fd = open(s_to_c(path), OREAD);
1023 	if(fd < 0)
1024 		return 0;
1025 
1026 	// march through raw message 4096 bytes at a time
1027 	// with a 128 byte overlap to chain the re search.
1028 	rv = 0;
1029 	for(;;){
1030 		i = read(fd, buf, sizeof(buf)-1);
1031 		if(i <= 0)
1032 			break;
1033 		buf[i] = 0;
1034 		if(regexec(prog, buf, nil, 0)){
1035 			rv = 1;
1036 			break;
1037 		}
1038 		if(i < sizeof(buf)-1)
1039 			break;
1040 		if(seek(fd, -128LL, 1) < 0)
1041 			break;
1042 	}
1043 
1044 	close(fd);
1045 	s_free(path);
1046 	return rv;
1047 }
1048 
1049 
1050 char*
1051 parsecmd(char *p, Cmd *cmd, Message *first, Message *cur)
1052 {
1053 	Reprog *prog;
1054 	Message *m, *s, *e, **l, *last;
1055 	char buf[256];
1056 	char *err;
1057 	int i, c;
1058 	char *q;
1059 	static char errbuf[Errlen];
1060 
1061 	cmd->delete = 0;
1062 	l = &cmd->msgs;
1063 	*l = nil;
1064 
1065 	// eat white space
1066 	while(*p == ' ')
1067 		p++;
1068 
1069 	// null command is a special case (advance and print)
1070 	if(*p == 0){
1071 		if(cur == &top){
1072 			// special case
1073 			m = first;
1074 		} else {
1075 			// walk to the next message even if we have to go up
1076 			m = cur->next;
1077 			while(m == nil && cur->parent != nil){
1078 				cur = cur->parent;
1079 				m = cur->next;
1080 			}
1081 		}
1082 		if(m == nil)
1083 			return "address";
1084 		*l = m;
1085 		m->cmd = nil;
1086 		cmd->an = 0;
1087 		cmd->f = pcmd;
1088 		return nil;
1089 	}
1090 
1091 	// global search ?
1092 	if(*p == 'g'){
1093 		p++;
1094 
1095 		// no search string means all messages
1096 		if(*p != '/' && *p != '%'){
1097 			for(m = first; m != nil; m = m->next){
1098 				*l = m;
1099 				l = &m->cmd;
1100 				*l = nil;
1101 			}
1102 		} else {
1103 			// mark all messages matching this search string
1104 			c = *p;
1105 			prog = parsesearch(&p);
1106 			if(prog == nil)
1107 				return "badly formed regular expression";
1108 			if(c == '%'){
1109 				for(m = first; m != nil; m = m->next){
1110 					if(rawsearch(m, prog)){
1111 						*l = m;
1112 						l = &m->cmd;
1113 						*l = nil;
1114 					}
1115 				}
1116 			} else {
1117 				for(m = first; m != nil; m = m->next){
1118 					snprintheader(buf, sizeof(buf), m);
1119 					if(regexec(prog, buf, nil, 0)){
1120 						*l = m;
1121 						l = &m->cmd;
1122 						*l = nil;
1123 					}
1124 				}
1125 			}
1126 			free(prog);
1127 		}
1128 	} else {
1129 
1130 		// parse an address
1131 		s = e = nil;
1132 		err = parseaddr(&p, first, cur, cur, &s);
1133 		if(err != nil)
1134 			return err;
1135 		if(*p == ','){
1136 			// this is an address range
1137 			if(s == &top)
1138 				s = first;
1139 			p++;
1140 			for(last = s; last != nil && last->next != nil; last = last->next)
1141 				;
1142 			err = parseaddr(&p, first, cur, last, &e);
1143 			if(err != nil)
1144 				return err;
1145 
1146 			// select all messages in the range
1147 			for(; s != nil; s = s->next){
1148 				*l = s;
1149 				l = &s->cmd;
1150 				*l = nil;
1151 				if(s == e)
1152 					break;
1153 			}
1154 			if(s == nil)
1155 				return "null address range";
1156 		} else {
1157 			// single address
1158 			if(s != &top){
1159 				*l = s;
1160 				s->cmd = nil;
1161 			}
1162 		}
1163 	}
1164 
1165 	// insert a space after '!'s and '|'s
1166 	for(q = p; *q; q++)
1167 		if(*q != '!' && *q != '|')
1168 			break;
1169 	if(q != p && *q != ' '){
1170 		memmove(q+1, q, strlen(q)+1);
1171 		*q = ' ';
1172 	}
1173 
1174 	cmd->an = getfields(p, cmd->av, nelem(cmd->av) - 1, 1, " \t\r\n");
1175 	if(cmd->an == 0 || *cmd->av[0] == 0)
1176 		cmd->f = pcmd;
1177 	else {
1178 		// hack to allow all messages to start with 'd'
1179 		if(*(cmd->av[0]) == 'd' && *(cmd->av[0]+1) != 0){
1180 			cmd->delete = 1;
1181 			cmd->av[0]++;
1182 		}
1183 
1184 		// search command table
1185 		for(i = 0; cmdtab[i].cmd != nil; i++)
1186 			if(strcmp(cmd->av[0], cmdtab[i].cmd) == 0)
1187 				break;
1188 		if(cmdtab[i].cmd == nil)
1189 			return "illegal command";
1190 		if(cmdtab[i].args == 0 && cmd->an > 1){
1191 			snprint(errbuf, sizeof(errbuf), "%s doesn't take an argument", cmdtab[i].cmd);
1192 			return errbuf;
1193 		}
1194 		cmd->f = cmdtab[i].f;
1195 	}
1196 	return nil;
1197 }
1198 
1199 // inefficient read from standard input
1200 char*
1201 readline(char *prompt, char *line, int len)
1202 {
1203 	char *p, *e;
1204 	int n;
1205 
1206 retry:
1207 	interrupted = 0;
1208 	Bprint(&out, "%s", prompt);
1209 	Bflush(&out);
1210 	e = line + len;
1211 	for(p = line; p < e; p++){
1212 		n = read(0, p, 1);
1213 		if(n < 0){
1214 			if(interrupted)
1215 				goto retry;
1216 			return nil;
1217 		}
1218 		if(n == 0)
1219 			return nil;
1220 		if(*p == '\n')
1221 			break;
1222 	}
1223 	*p = 0;
1224 	return line;
1225 }
1226 
1227 void
1228 messagecount(Message *m)
1229 {
1230 	int i;
1231 
1232 	i = 0;
1233 	for(; m != nil; m = m->next)
1234 		i++;
1235 	Bprint(&out, "%d message%s\n", i, plural(i));
1236 }
1237 
1238 Message*
1239 aichcmd(Message *m, int indent)
1240 {
1241 	char	hdr[256];
1242 
1243 	if(m == &top)
1244 		return nil;
1245 
1246 	snprintHeader(hdr, sizeof(hdr), indent, m);
1247 	Bprint(&out, "%s\n", hdr);
1248 	for(m = m->child; m != nil; m = m->next)
1249 		aichcmd(m, indent+1);
1250 	return nil;
1251 }
1252 
1253 Message*
1254 Hcmd(Cmd*, Message *m)
1255 {
1256 	if(m == &top)
1257 		return nil;
1258 	aichcmd(m, 0);
1259 	return nil;
1260 }
1261 
1262 Message*
1263 hcmd(Cmd*, Message *m)
1264 {
1265 	char	hdr[256];
1266 
1267 	if(m == &top)
1268 		return nil;
1269 
1270 	snprintheader(hdr, sizeof(hdr), m);
1271 	Bprint(&out, "%s\n", hdr);
1272 	return nil;
1273 }
1274 
1275 Message*
1276 bcmd(Cmd*, Message *m)
1277 {
1278 	int i;
1279 	Message *om = m;
1280 
1281 	if(m == &top)
1282 		m = top.child;
1283 	for(i = 0; i < 10 && m != nil; i++){
1284 		hcmd(nil, m);
1285 		om = m;
1286 		m = m->next;
1287 	}
1288 
1289 	return om;
1290 }
1291 
1292 Message*
1293 ncmd(Cmd*, Message *m)
1294 {
1295 	if(m == &top)
1296 		return m->child;
1297 	return m->next;
1298 }
1299 
1300 int
1301 printpart(String *s, char *part)
1302 {
1303 	char buf[4096];
1304 	int n, fd, tot;
1305 	String *path;
1306 
1307 	path = extendpath(s, part);
1308 	fd = open(s_to_c(path), OREAD);
1309 	s_free(path);
1310 	if(fd < 0){
1311 		fprint(2, "!message disappeared\n");
1312 		return 0;
1313 	}
1314 	tot = 0;
1315 	while((n = read(fd, buf, sizeof(buf))) > 0){
1316 		if(interrupted)
1317 			break;
1318 		if(Bwrite(&out, buf, n) <= 0)
1319 			break;
1320 		tot += n;
1321 	}
1322 	close(fd);
1323 	return tot;
1324 }
1325 
1326 int
1327 printhtml(Message *m)
1328 {
1329 	Cmd c;
1330 
1331 	c.an = 3;
1332 	c.av[1] = "/bin/htmlfmt";
1333 	c.av[2] = "-l 40 -cutf-8";
1334 	Bprint(&out, "!%s\n", c.av[1]);
1335 	Bflush(&out);
1336 	pipecmd(&c, m);
1337 	return 0;
1338 }
1339 
1340 Message*
1341 Pcmd(Cmd*, Message *m)
1342 {
1343 	if(m == &top)
1344 		return &top;
1345 	if(m->parent == &top)
1346 		printpart(m->path, "unixheader");
1347 	printpart(m->path, "raw");
1348 	return m;
1349 }
1350 
1351 void
1352 compress(char *p)
1353 {
1354 	char *np;
1355 	int last;
1356 
1357 	last = ' ';
1358 	for(np = p; *p; p++){
1359 		if(*p != ' ' || last != ' '){
1360 			last = *p;
1361 			*np++ = last;
1362 		}
1363 	}
1364 	*np = 0;
1365 }
1366 
1367 Message*
1368 pcmd(Cmd*, Message *m)
1369 {
1370 	Message *nm;
1371 	Ctype *cp;
1372 	String *s;
1373 	char buf[128];
1374 
1375 	if(m == &top)
1376 		return &top;
1377 	if(m->parent == &top)
1378 		printpart(m->path, "unixheader");
1379 	if(printpart(m->path, "header") > 0)
1380 		Bprint(&out, "\n");
1381 	cp = findctype(m);
1382 	if(cp->display){
1383 		if(strcmp(m->type, "text/html") == 0)
1384 			printhtml(m);
1385 		else
1386 			printpart(m->path, "body");
1387 	} else if(strcmp(m->type, "multipart/alternative") == 0){
1388 		for(nm = m->child; nm != nil; nm = nm->next){
1389 			cp = findctype(nm);
1390 			if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
1391 				break;
1392 		}
1393 		if(nm == nil)
1394 			for(nm = m->child; nm != nil; nm = nm->next){
1395 				cp = findctype(nm);
1396 				if(cp->display)
1397 					break;
1398 			}
1399 		if(nm != nil)
1400 			pcmd(nil, nm);
1401 		else
1402 			hcmd(nil, m);
1403 	} else if(strncmp(m->type, "multipart/", 10) == 0){
1404 		nm = m->child;
1405 		if(nm != nil){
1406 			// always print first part
1407 			pcmd(nil, nm);
1408 
1409 			for(nm = nm->next; nm != nil; nm = nm->next){
1410 				s = rooted(s_clone(nm->path));
1411 				cp = findctype(nm);
1412 				snprintHeader(buf, sizeof buf, -1, nm);
1413 				compress(buf);
1414 				if(strcmp(nm->disposition, "inline") == 0){
1415 					if(cp->ext != nil)
1416 						Bprint(&out, "\n--- %s %s/body.%s\n\n",
1417 							buf, s_to_c(s), cp->ext);
1418 					else
1419 						Bprint(&out, "\n--- %s %s/body\n\n",
1420 							buf, s_to_c(s));
1421 					pcmd(nil, nm);
1422 				} else {
1423 					if(cp->ext != nil)
1424 						Bprint(&out, "\n!--- %s %s/body.%s\n",
1425 							buf, s_to_c(s), cp->ext);
1426 					else
1427 						Bprint(&out, "\n!--- %s %s/body\n",
1428 							buf, s_to_c(s));
1429 				}
1430 				s_free(s);
1431 			}
1432 		} else {
1433 			hcmd(nil, m);
1434 		}
1435 	} else if(strcmp(m->type, "message/rfc822") == 0){
1436 		pcmd(nil, m->child);
1437 	} else if(plumb(m, cp) >= 0)
1438 		Bprint(&out, "\n!--- using plumber to display message of type %s\n", m->type);
1439 	else
1440 		Bprint(&out, "\n!--- cannot display messages of type %s\n", m->type);
1441 
1442 	return m;
1443 }
1444 
1445 void
1446 printpartindented(String *s, char *part, char *indent)
1447 {
1448 	char *p;
1449 	String *path;
1450 	Biobuf *b;
1451 
1452 	path = extendpath(s, part);
1453 	b = Bopen(s_to_c(path), OREAD);
1454 	s_free(path);
1455 	if(b == nil){
1456 		fprint(2, "!message disappeared\n");
1457 		return;
1458 	}
1459 	while((p = Brdline(b, '\n')) != nil){
1460 		if(interrupted)
1461 			break;
1462 		p[Blinelen(b)-1] = 0;
1463 		if(Bprint(&out, "%s%s\n", indent, p) < 0)
1464 			break;
1465 	}
1466 	Bprint(&out, "\n");
1467 	Bterm(b);
1468 }
1469 
1470 Message*
1471 quotecmd(Cmd*, Message *m)
1472 {
1473 	Message *nm;
1474 	Ctype *cp;
1475 
1476 	if(m == &top)
1477 		return &top;
1478 	Bprint(&out, "\n");
1479 	if(m->from != nil && *m->from)
1480 		Bprint(&out, "On %s, %s wrote:\n", m->date, m->from);
1481 	cp = findctype(m);
1482 	if(cp->display){
1483 		printpartindented(m->path, "body", "> ");
1484 	} else if(strcmp(m->type, "multipart/alternative") == 0){
1485 		for(nm = m->child; nm != nil; nm = nm->next){
1486 			cp = findctype(nm);
1487 			if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
1488 				break;
1489 		}
1490 		if(nm == nil)
1491 			for(nm = m->child; nm != nil; nm = nm->next){
1492 				cp = findctype(nm);
1493 				if(cp->display)
1494 					break;
1495 			}
1496 		if(nm != nil)
1497 			quotecmd(nil, nm);
1498 	} else if(strncmp(m->type, "multipart/", 10) == 0){
1499 		nm = m->child;
1500 		if(nm != nil){
1501 			cp = findctype(nm);
1502 			if(cp->display || strncmp(m->type, "multipart/", 10) == 0)
1503 				quotecmd(nil, nm);
1504 		}
1505 	}
1506 	return m;
1507 }
1508 
1509 // really delete messages
1510 Message*
1511 flushdeleted(Message *cur)
1512 {
1513 	Message *m, **l;
1514 	char buf[1024], *p, *e, *msg;
1515 	int deld, n, fd;
1516 	int i;
1517 
1518 	doflush = 0;
1519 	deld = 0;
1520 
1521 	fd = open("/mail/fs/ctl", ORDWR);
1522 	if(fd < 0){
1523 		fprint(2, "!can't delete mail, opening /mail/fs/ctl: %r\n");
1524 		exitfs(0);
1525 	}
1526 	e = &buf[sizeof(buf)];
1527 	p = seprint(buf, e, "delete %s", mbname);
1528 	n = 0;
1529 	for(l = &top.child; *l != nil;){
1530 		m = *l;
1531 		if(!m->deleted){
1532 			l = &(*l)->next;
1533 			continue;
1534 		}
1535 
1536 		// don't return a pointer to a deleted message
1537 		if(m == cur)
1538 			cur = m->next;
1539 
1540 		deld++;
1541 		msg = strrchr(s_to_c(m->path), '/');
1542 		if(msg == nil)
1543 			msg = s_to_c(m->path);
1544 		else
1545 			msg++;
1546 		if(e-p < 10){
1547 			write(fd, buf, p-buf);
1548 			n = 0;
1549 			p = seprint(buf, e, "delete %s", mbname);
1550 		}
1551 		p = seprint(p, e, " %s", msg);
1552 		n++;
1553 
1554 		// unchain and free
1555 		*l = m->next;
1556 		if(m->next)
1557 			m->next->prev = m->prev;
1558 		freemessage(m);
1559 	}
1560 	if(n)
1561 		write(fd, buf, p-buf);
1562 
1563 	close(fd);
1564 
1565 	if(deld)
1566 		Bprint(&out, "!%d message%s deleted\n", deld, plural(deld));
1567 
1568 	// renumber
1569 	i = 1;
1570 	for(m = top.child; m != nil; m = m->next)
1571 		m->id = natural ? m->fileno : i++;
1572 
1573 	// if we're out of messages, go back to first
1574 	// if no first, return the fake first
1575 	if(cur == nil){
1576 		if(top.child)
1577 			return top.child;
1578 		else
1579 			return &top;
1580 	}
1581 	return cur;
1582 }
1583 
1584 Message*
1585 qcmd(Cmd*, Message*)
1586 {
1587 	flushdeleted(nil);
1588 
1589 	if(didopen)
1590 		closemb();
1591 	Bflush(&out);
1592 
1593 	exitfs(0);
1594 	return nil;	// not reached
1595 }
1596 
1597 Message*
1598 ycmd(Cmd*, Message *m)
1599 {
1600 	doflush = 1;
1601 
1602 	return icmd(nil, m);
1603 }
1604 
1605 Message*
1606 xcmd(Cmd*, Message*)
1607 {
1608 	exitfs(0);
1609 	return nil;	// not reached
1610 }
1611 
1612 Message*
1613 eqcmd(Cmd*, Message *m)
1614 {
1615 	if(m == &top)
1616 		Bprint(&out, "0\n");
1617 	else
1618 		Bprint(&out, "%d\n", m->id);
1619 	return nil;
1620 }
1621 
1622 Message*
1623 dcmd(Cmd*, Message *m)
1624 {
1625 	if(m == &top){
1626 		Bprint(&out, "!address\n");
1627 		return nil;
1628 	}
1629 	while(m->parent != &top)
1630 		m = m->parent;
1631 	m->deleted = 1;
1632 	return m;
1633 }
1634 
1635 Message*
1636 ucmd(Cmd*, Message *m)
1637 {
1638 	if(m == &top)
1639 		return nil;
1640 	while(m->parent != &top)
1641 		m = m->parent;
1642 	if(m->deleted < 0)
1643 		Bprint(&out, "!can't undelete, already flushed\n");
1644 	m->deleted = 0;
1645 	return m;
1646 }
1647 
1648 
1649 Message*
1650 icmd(Cmd*, Message *m)
1651 {
1652 	int n;
1653 
1654 	n = dir2message(&top, reverse);
1655 	if(n > 0)
1656 		Bprint(&out, "%d new message%s\n", n, plural(n));
1657 	return m;
1658 }
1659 
1660 Message*
1661 helpcmd(Cmd*, Message *m)
1662 {
1663 	int i;
1664 
1665 	Bprint(&out, "Commands are of the form [<range>] <command> [args]\n");
1666 	Bprint(&out, "<range> := <addr> | <addr>','<addr>| 'g'<search>\n");
1667 	Bprint(&out, "<addr> := '.' | '$' | '^' | <number> | <search> | <addr>'+'<addr> | <addr>'-'<addr>\n");
1668 	Bprint(&out, "<search> := '/'<regexp>'/' | '?'<regexp>'?' | '%%'<regexp>'%%'\n");
1669 	Bprint(&out, "<command> :=\n");
1670 	for(i = 0; cmdtab[i].cmd != nil; i++)
1671 		Bprint(&out, "%s\n", cmdtab[i].help);
1672 	return m;
1673 }
1674 
1675 int
1676 tomailer(char **av)
1677 {
1678 	Waitmsg *w;
1679 	int pid, i;
1680 
1681 	// start the mailer and get out of the way
1682 	switch(pid = fork()){
1683 	case -1:
1684 		fprint(2, "can't fork: %r\n");
1685 		return -1;
1686 	case 0:
1687 		Bprint(&out, "!/bin/upas/marshal");
1688 		for(i = 1; av[i]; i++){
1689 			if(strchr(av[i], ' ') != nil)
1690 				Bprint(&out, " '%s'", av[i]);
1691 			else
1692 				Bprint(&out, " %s", av[i]);
1693 		}
1694 		Bprint(&out, "\n");
1695 		Bflush(&out);
1696 		av[0] = "marshal";
1697 		chdir(wd);
1698 		exec("/bin/upas/marshal", av);
1699 		fprint(2, "couldn't exec /bin/upas/marshal\n");
1700 		exits(0);
1701 	default:
1702 		w = wait();
1703 		if(w == nil){
1704 			if(interrupted)
1705 				postnote(PNPROC, pid, "die");
1706 			waitpid();
1707 			return -1;
1708 		}
1709 		if(w->msg[0]){
1710 			fprint(2, "mailer failed: %s\n", w->msg);
1711 			free(w);
1712 			return -1;
1713 		}
1714 		free(w);
1715 		Bprint(&out, "!\n");
1716 		break;
1717 	}
1718 	return 0;
1719 }
1720 
1721 //
1722 // like tokenize but obey "" quoting
1723 //
1724 int
1725 tokenize822(char *str, char **args, int max)
1726 {
1727 	int na;
1728 	int intok = 0, inquote = 0;
1729 
1730 	if(max <= 0)
1731 		return 0;
1732 	for(na=0; ;str++)
1733 		switch(*str) {
1734 		case ' ':
1735 		case '\t':
1736 			if(inquote)
1737 				goto Default;
1738 			/* fall through */
1739 		case '\n':
1740 			*str = 0;
1741 			if(!intok)
1742 				continue;
1743 			intok = 0;
1744 			if(na < max)
1745 				continue;
1746 			/* fall through */
1747 		case 0:
1748 			return na;
1749 		case '"':
1750 			inquote ^= 1;
1751 			/* fall through */
1752 		Default:
1753 		default:
1754 			if(intok)
1755 				continue;
1756 			args[na++] = str;
1757 			intok = 1;
1758 		}
1759 }
1760 
1761 /* return reply-to address & set *nmp to corresponding Message */
1762 static char *
1763 getreplyto(Message *m, Message **nmp)
1764 {
1765 	Message *nm;
1766 
1767 	for(nm = m; nm != &top; nm = nm->parent)
1768  		if(*nm->replyto != 0)
1769 			break;
1770 	*nmp = nm;
1771 	return nm? nm->replyto: nil;
1772 }
1773 
1774 Message*
1775 rcmd(Cmd *c, Message *m)
1776 {
1777 	char *addr;
1778 	char *av[128];
1779 	int i, ai = 1;
1780 	String *from, *rpath, *path = nil, *subject = nil;
1781 	Message *nm;
1782 
1783 	if(m == &top){
1784 		Bprint(&out, "!address\n");
1785 		return nil;
1786 	}
1787 
1788 	addr = getreplyto(m, &nm);
1789 	if(addr == nil){
1790 		Bprint(&out, "!no reply address\n");
1791 		return nil;
1792 	}
1793 	if(nm == &top){
1794 		print("!noone to reply to\n");
1795 		return nil;
1796 	}
1797 
1798 	for(nm = m; nm != &top; nm = nm->parent){
1799 		if(*nm->subject){
1800 			av[ai++] = "-s";
1801 			subject = addrecolon(nm->subject);
1802 			av[ai++] = s_to_c(subject);
1803 			break;
1804 		}
1805 	}
1806 
1807 	av[ai++] = "-R";
1808 	rpath = rooted(s_clone(m->path));
1809 	av[ai++] = s_to_c(rpath);
1810 
1811 	if(strchr(c->av[0], 'f') != nil){
1812 		fcmd(c, m);
1813 		av[ai++] = "-F";
1814 	}
1815 
1816 	if(strchr(c->av[0], 'R') != nil){
1817 		av[ai++] = "-t";
1818 		av[ai++] = "message/rfc822";
1819 		av[ai++] = "-A";
1820 		path = rooted(extendpath(m->path, "raw"));
1821 		av[ai++] = s_to_c(path);
1822 	}
1823 
1824 	for(i = 1; i < c->an && ai < nelem(av)-1; i++)
1825 		av[ai++] = c->av[i];
1826 	from = s_copy(addr);
1827 	ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
1828 	av[ai] = 0;
1829 	if(tomailer(av) < 0)
1830 		m = nil;
1831 	s_free(path);
1832 	s_free(rpath);
1833 	s_free(subject);
1834 	s_free(from);
1835 	return m;
1836 }
1837 
1838 Message*
1839 mcmd(Cmd *c, Message *m)
1840 {
1841 	char **av;
1842 	int i, ai;
1843 	String *path;
1844 
1845 	if(m == &top){
1846 		Bprint(&out, "!address\n");
1847 		return nil;
1848 	}
1849 
1850 	if(c->an < 2){
1851 		fprint(2, "!usage: M list-of addresses\n");
1852 		return nil;
1853 	}
1854 
1855 	ai = 1;
1856 	av = malloc(sizeof(char*)*(c->an + 8));
1857 
1858 	av[ai++] = "-t";
1859 	if(m->parent == &top)
1860 		av[ai++] = "message/rfc822";
1861 	else
1862 		av[ai++] = "mime";
1863 
1864 	av[ai++] = "-A";
1865 	path = rooted(extendpath(m->path, "raw"));
1866 	av[ai++] = s_to_c(path);
1867 
1868 	if(strchr(c->av[0], 'M') == nil)
1869 		av[ai++] = "-n";
1870 
1871 	for(i = 1; i < c->an; i++)
1872 		av[ai++] = c->av[i];
1873 	av[ai] = 0;
1874 
1875 	if(tomailer(av) < 0)
1876 		m = nil;
1877 	if(path != nil)
1878 		s_free(path);
1879 	free(av);
1880 	return m;
1881 }
1882 
1883 Message*
1884 acmd(Cmd *c, Message *m)
1885 {
1886 	char *av[128];
1887 	int i, ai = 1;
1888 	String *from, *rpath, *path = nil, *subject = nil;
1889 	String *to, *cc;
1890 
1891 	if(m == &top){
1892 		Bprint(&out, "!address\n");
1893 		return nil;
1894 	}
1895 
1896 	if(*m->subject){
1897 		av[ai++] = "-s";
1898 		subject = addrecolon(m->subject);
1899 		av[ai++] = s_to_c(subject);
1900 	}
1901 
1902 	av[ai++] = "-R";
1903 	rpath = rooted(s_clone(m->path));
1904 	av[ai++] = s_to_c(rpath);
1905 
1906 	if(strchr(c->av[0], 'A') != nil){
1907 		av[ai++] = "-t";
1908 		av[ai++] = "message/rfc822";
1909 		av[ai++] = "-A";
1910 		path = rooted(extendpath(m->path, "raw"));
1911 		av[ai++] = s_to_c(path);
1912 	}
1913 
1914 	for(i = 1; i < c->an && ai < nelem(av)-1; i++)
1915 		av[ai++] = c->av[i];
1916 	from = s_copy(m->from);
1917 	ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
1918 	to = s_copy(m->to);
1919 	ai += tokenize822(s_to_c(to), &av[ai], nelem(av) - ai);
1920 	cc = s_copy(m->cc);
1921 	ai += tokenize822(s_to_c(cc), &av[ai], nelem(av) - ai);
1922 	av[ai] = 0;
1923 	if(tomailer(av) < 0)
1924 		m = nil;
1925 	s_free(path);
1926 	s_free(rpath);
1927 	s_free(subject);
1928 	s_free(from);
1929 	s_free(to);
1930 	s_free(cc);
1931 	return m;
1932 }
1933 
1934 String *
1935 relpath(char *path, String *to)
1936 {
1937 	if (*path=='/' || strncmp(path, "./", 2) == 0
1938 			      || strncmp(path, "../", 3) == 0) {
1939 		to = s_append(to, path);
1940 	} else if(mbpath) {
1941 		to = s_append(to, s_to_c(mbpath));
1942 		to->ptr = strrchr(to->base, '/')+1;
1943 		s_append(to, path);
1944 	}
1945 	return to;
1946 }
1947 
1948 int
1949 appendtofile(Message *m, char *part, char *base, int mbox)
1950 {
1951 	String *file, *h;
1952 	int in, out, rv;
1953 
1954 	file = extendpath(m->path, part);
1955 	in = open(s_to_c(file), OREAD);
1956 	if(in < 0){
1957 		fprint(2, "!message disappeared\n");
1958 		return -1;
1959 	}
1960 
1961 	s_reset(file);
1962 
1963 	relpath(base, file);
1964 	if(sysisdir(s_to_c(file))){
1965 		s_append(file, "/");
1966 		if(m->filename && strchr(m->filename, '/') == nil)
1967 			s_append(file, m->filename);
1968 		else {
1969 			s_append(file, "att.XXXXXXXXXXX");
1970 			mktemp(s_to_c(file));
1971 		}
1972 	}
1973 	if(mbox)
1974 		out = open(s_to_c(file), OWRITE);
1975 	else
1976 		out = open(s_to_c(file), OWRITE|OTRUNC);
1977 	if(out < 0){
1978 		out = create(s_to_c(file), OWRITE, 0666);
1979 		if(out < 0){
1980 			fprint(2, "!can't open %s: %r\n", s_to_c(file));
1981 			close(in);
1982 			s_free(file);
1983 			return -1;
1984 		}
1985 	}
1986 	if(mbox)
1987 		seek(out, 0, 2);
1988 
1989 	// put on a 'From ' line
1990 	if(mbox){
1991 		while(m->parent != &top)
1992 			m = m->parent;
1993 		h = file2string(m->path, "unixheader");
1994 		fprint(out, "%s", s_to_c(h));
1995 		s_free(h);
1996 	}
1997 
1998 	// copy the message escaping what we have to ad adding newlines if we have to
1999 	if(mbox)
2000 		rv = appendfiletombox(in, out);
2001 	else
2002 		rv = appendfiletofile(in, out);
2003 
2004 	close(in);
2005 	close(out);
2006 
2007 	if(rv >= 0)
2008 		print("!saved in %s\n", s_to_c(file));
2009 	s_free(file);
2010 	return rv;
2011 }
2012 
2013 Message*
2014 scmd(Cmd *c, Message *m)
2015 {
2016 	char *file;
2017 
2018 	if(m == &top){
2019 		Bprint(&out, "!address\n");
2020 		return nil;
2021 	}
2022 
2023 	switch(c->an){
2024 	case 1:
2025 		file = "stored";
2026 		break;
2027 	case 2:
2028 		file = c->av[1];
2029 		break;
2030 	default:
2031 		fprint(2, "!usage: s filename\n");
2032 		return nil;
2033 	}
2034 
2035 	if(appendtofile(m, "raw", file, 1) < 0)
2036 		return nil;
2037 
2038 	m->stored = 1;
2039 	return m;
2040 }
2041 
2042 Message*
2043 wcmd(Cmd *c, Message *m)
2044 {
2045 	char *file;
2046 
2047 	if(m == &top){
2048 		Bprint(&out, "!address\n");
2049 		return nil;
2050 	}
2051 
2052 	switch(c->an){
2053 	case 2:
2054 		file = c->av[1];
2055 		break;
2056 	case 1:
2057 		if(*m->filename == 0){
2058 			fprint(2, "!usage: w filename\n");
2059 			return nil;
2060 		}
2061 		file = strrchr(m->filename, '/');
2062 		if(file != nil)
2063 			file++;
2064 		else
2065 			file = m->filename;
2066 		break;
2067 	default:
2068 		fprint(2, "!usage: w filename\n");
2069 		return nil;
2070 	}
2071 
2072 	if(appendtofile(m, "body", file, 0) < 0)
2073 		return nil;
2074 	m->stored = 1;
2075 	return m;
2076 }
2077 
2078 char *specialfile[] =
2079 {
2080 	"pipeto",
2081 	"pipefrom",
2082 	"L.mbox",
2083 	"forward",
2084 	"names"
2085 };
2086 
2087 // return 1 if this is a special file
2088 static int
2089 special(String *s)
2090 {
2091 	char *p;
2092 	int i;
2093 
2094 	p = strrchr(s_to_c(s), '/');
2095 	if(p == nil)
2096 		p = s_to_c(s);
2097 	else
2098 		p++;
2099 	for(i = 0; i < nelem(specialfile); i++)
2100 		if(strcmp(p, specialfile[i]) == 0)
2101 			return 1;
2102 	return 0;
2103 }
2104 
2105 // open the folder using the recipients account name
2106 static String*
2107 foldername(char *rcvr)
2108 {
2109 	char *p;
2110 	int c;
2111 	String *file;
2112 	Dir *d;
2113 	int scarey;
2114 
2115 	file = s_new();
2116 	mboxpath("f", user, file, 0);
2117 	d = dirstat(s_to_c(file));
2118 
2119 	// if $mail/f exists, store there, otherwise in $mail
2120 	s_restart(file);
2121 	if(d && d->qid.type == QTDIR){
2122 		scarey = 0;
2123 		s_append(file, "f/");
2124 	} else {
2125 		scarey = 1;
2126 	}
2127 	free(d);
2128 
2129 	p = strrchr(rcvr, '!');
2130 	if(p != nil)
2131 		rcvr = p+1;
2132 
2133 	while(*rcvr && *rcvr != '@'){
2134 		c = *rcvr++;
2135 		if(c == '/')
2136 			c = '_';
2137 		s_putc(file, c);
2138 	}
2139 	s_terminate(file);
2140 
2141 	if(scarey && special(file)){
2142 		fprint(2, "!won't overwrite %s\n", s_to_c(file));
2143 		s_free(file);
2144 		return nil;
2145 	}
2146 
2147 	return file;
2148 }
2149 
2150 Message*
2151 fcmd(Cmd *c, Message *m)
2152 {
2153 	String *folder;
2154 
2155 	if(c->an > 1){
2156 		fprint(2, "!usage: f takes no arguments\n");
2157 		return nil;
2158 	}
2159 
2160 	if(m == &top){
2161 		Bprint(&out, "!address\n");
2162 		return nil;
2163 	}
2164 
2165 	folder = foldername(m->from);
2166 	if(folder == nil)
2167 		return nil;
2168 
2169 	if(appendtofile(m, "raw", s_to_c(folder), 1) < 0){
2170 		s_free(folder);
2171 		return nil;
2172 	}
2173 	s_free(folder);
2174 
2175 	m->stored = 1;
2176 	return m;
2177 }
2178 
2179 void
2180 system(char *cmd, char **av, int in)
2181 {
2182 	int pid;
2183 
2184 	switch(pid=fork()){
2185 	case -1:
2186 		return;
2187 	case 0:
2188 		if(in >= 0){
2189 			close(0);
2190 			dup(in, 0);
2191 			close(in);
2192 		}
2193 		if(wd[0] != 0)
2194 			chdir(wd);
2195 		exec(cmd, av);
2196 		fprint(2, "!couldn't exec %s\n", cmd);
2197 		exits(0);
2198 	default:
2199 		if(in >= 0)
2200 			close(in);
2201 		while(waitpid() < 0){
2202 			if(!interrupted)
2203 				break;
2204 			postnote(PNPROC, pid, "die");
2205 			continue;
2206 		}
2207 		break;
2208 	}
2209 }
2210 
2211 Message*
2212 bangcmd(Cmd *c, Message *m)
2213 {
2214 	char cmd[4*1024];
2215 	char *p, *e;
2216 	char *av[4];
2217 	int i;
2218 
2219 	cmd[0] = 0;
2220 	p = cmd;
2221 	e = cmd+sizeof(cmd);
2222 	for(i = 1; i < c->an; i++)
2223 		p = seprint(p, e, "%s ", c->av[i]);
2224 	av[0] = "rc";
2225 	av[1] = "-c";
2226 	av[2] = cmd;
2227 	av[3] = 0;
2228 	system("/bin/rc", av, -1);
2229 	Bprint(&out, "!\n");
2230 	return m;
2231 }
2232 
2233 Message*
2234 xpipecmd(Cmd *c, Message *m, char *part)
2235 {
2236 	char cmd[128];
2237 	char *p, *e;
2238 	char *av[4];
2239 	String *path;
2240 	int i, fd;
2241 
2242 	if(c->an < 2){
2243 		Bprint(&out, "!usage: | cmd\n");
2244 		return nil;
2245 	}
2246 
2247 	if(m == &top){
2248 		Bprint(&out, "!address\n");
2249 		return nil;
2250 	}
2251 
2252 	path = extendpath(m->path, part);
2253 	fd = open(s_to_c(path), OREAD);
2254 	s_free(path);
2255 	if(fd < 0){	// compatibility with older upas/fs
2256 		path = extendpath(m->path, "raw");
2257 		fd = open(s_to_c(path), OREAD);
2258 		s_free(path);
2259 	}
2260 	if(fd < 0){
2261 		fprint(2, "!message disappeared\n");
2262 		return nil;
2263 	}
2264 
2265 	p = cmd;
2266 	e = cmd+sizeof(cmd);
2267 	cmd[0] = 0;
2268 	for(i = 1; i < c->an; i++)
2269 		p = seprint(p, e, "%s ", c->av[i]);
2270 	av[0] = "rc";
2271 	av[1] = "-c";
2272 	av[2] = cmd;
2273 	av[3] = 0;
2274 	system("/bin/rc", av, fd);	/* system closes fd */
2275 	Bprint(&out, "!\n");
2276 	return m;
2277 }
2278 
2279 Message*
2280 pipecmd(Cmd *c, Message *m)
2281 {
2282 	return xpipecmd(c, m, "body");
2283 }
2284 
2285 Message*
2286 rpipecmd(Cmd *c, Message *m)
2287 {
2288 	return xpipecmd(c, m, "rawunix");
2289 }
2290 
2291 void
2292 closemb(void)
2293 {
2294 	int fd;
2295 
2296 	fd = open("/mail/fs/ctl", ORDWR);
2297 	if(fd < 0)
2298 		sysfatal("can't open /mail/fs/ctl: %r");
2299 
2300 	// close current mailbox
2301 	if(*mbname && strcmp(mbname, "mbox") != 0)
2302 		fprint(fd, "close %s", mbname);
2303 
2304 	close(fd);
2305 }
2306 
2307 int
2308 switchmb(char *file, char *singleton)
2309 {
2310 	char *p;
2311 	int n, fd;
2312 	String *path;
2313 	char buf[256];
2314 
2315 	// if the user didn't say anything and there
2316 	// is an mbox mounted already, use that one
2317 	// so that the upas/fs -fdefault default is honored.
2318 	if(file
2319 	|| (singleton && access(singleton, 0)<0)
2320 	|| (!singleton && access("/mail/fs/mbox", 0)<0)){
2321 		if(file == nil)
2322 			file = "mbox";
2323 
2324 		// close current mailbox
2325 		closemb();
2326 		didopen = 1;
2327 
2328 		fd = open("/mail/fs/ctl", ORDWR);
2329 		if(fd < 0)
2330 			sysfatal("can't open /mail/fs/ctl: %r");
2331 
2332 		path = s_new();
2333 
2334 		// get an absolute path to the mail box
2335 		if(strncmp(file, "./", 2) == 0){
2336 			// resolve path here since upas/fs doesn't know
2337 			// our working directory
2338 			if(getwd(buf, sizeof(buf)-strlen(file)) == nil){
2339 				fprint(2, "!can't get working directory: %s\n", buf);
2340 				return -1;
2341 			}
2342 			s_append(path, buf);
2343 			s_append(path, file+1);
2344 		} else {
2345 			mboxpath(file, user, path, 0);
2346 		}
2347 
2348 		// make up a handle to use when talking to fs
2349 		p = strrchr(file, '/');
2350 		if(p == nil){
2351 			// if its in the mailbox directory, just use the name
2352 			strncpy(mbname, file, sizeof(mbname));
2353 			mbname[sizeof(mbname)-1] = 0;
2354 		} else {
2355 			// make up a mailbox name
2356 			p = strrchr(s_to_c(path), '/');
2357 			p++;
2358 			if(*p == 0){
2359 				fprint(2, "!bad mbox name");
2360 				return -1;
2361 			}
2362 			strncpy(mbname, p, sizeof(mbname));
2363 			mbname[sizeof(mbname)-1] = 0;
2364 			n = strlen(mbname);
2365 			if(n > Elemlen-12)
2366 				n = Elemlen-12;
2367 			sprint(mbname+n, "%ld", time(0));
2368 		}
2369 
2370 		if(fprint(fd, "open %s %s", s_to_c(path), mbname) < 0){
2371 			fprint(2, "!can't 'open %s %s': %r\n", file, mbname);
2372 			s_free(path);
2373 			return -1;
2374 		}
2375 		close(fd);
2376 	}else
2377 	if (singleton && access(singleton, 0)==0
2378 	    && strncmp(singleton, "/mail/fs/", 9) == 0){
2379 		if ((p = strchr(singleton +10, '/')) == nil){
2380 			fprint(2, "!bad mbox name");
2381 			return -1;
2382 		}
2383 		n = p-(singleton+9);
2384 		strncpy(mbname, singleton+9, n);
2385 		mbname[n+1] = 0;
2386 		path = s_reset(nil);
2387 		mboxpath(mbname, user, path, 0);
2388 	}else{
2389 		path = s_reset(nil);
2390 		mboxpath("mbox", user, path, 0);
2391 		strcpy(mbname, "mbox");
2392 	}
2393 
2394 	sprint(root, "/mail/fs/%s", mbname);
2395 	if(getwd(wd, sizeof(wd)) == 0)
2396 		wd[0] = 0;
2397 	if(singleton == nil && chdir(root) >= 0)
2398 		strcpy(root, ".");
2399 	rootlen = strlen(root);
2400 
2401 	if(mbpath != nil)
2402 		s_free(mbpath);
2403 	mbpath = path;
2404 	return 0;
2405 }
2406 
2407 // like tokenize but for into lines
2408 int
2409 lineize(char *s, char **f, int n)
2410 {
2411 	int i;
2412 
2413 	for(i = 0; *s && i < n; i++){
2414 		f[i] = s;
2415 		s = strchr(s, '\n');
2416 		if(s == nil)
2417 			break;
2418 		*s++ = 0;
2419 	}
2420 	return i;
2421 }
2422 
2423 
2424 
2425 String*
2426 rooted(String *s)
2427 {
2428 	static char buf[256];
2429 
2430 	if(strcmp(root, ".") != 0)
2431 		return s;
2432 	snprint(buf, sizeof(buf), "/mail/fs/%s/%s", mbname, s_to_c(s));
2433 	s_free(s);
2434 	return s_copy(buf);
2435 }
2436 
2437 int
2438 plumb(Message *m, Ctype *cp)
2439 {
2440 	String *s;
2441 	Plumbmsg *pm;
2442 	static int fd = -2;
2443 
2444 	if(cp->plumbdest == nil)
2445 		return -1;
2446 
2447 	if(fd < -1)
2448 		fd = plumbopen("send", OWRITE);
2449 	if(fd < 0)
2450 		return -1;
2451 
2452 	pm = mallocz(sizeof(Plumbmsg), 1);
2453 	pm->src = strdup("mail");
2454 	if(*cp->plumbdest)
2455 		pm->dst = strdup(cp->plumbdest);
2456 	pm->wdir = nil;
2457 	pm->type = strdup("text");
2458 	pm->ndata = -1;
2459 	s = rooted(extendpath(m->path, "body"));
2460 	if(cp->ext != nil){
2461 		s_append(s, ".");
2462 		s_append(s, cp->ext);
2463 	}
2464 	pm->data = strdup(s_to_c(s));
2465 	s_free(s);
2466 	plumbsend(fd, pm);
2467 	plumbfree(pm);
2468 	return 0;
2469 }
2470 
2471 void
2472 regerror(char*)
2473 {
2474 }
2475 
2476 String*
2477 addrecolon(char *s)
2478 {
2479 	String *str;
2480 
2481 	if(cistrncmp(s, "re:", 3) != 0){
2482 		str = s_copy("Re: ");
2483 		s_append(str, s);
2484 	} else
2485 		str = s_copy(s);
2486 	return str;
2487 }
2488 
2489 void
2490 exitfs(char *rv)
2491 {
2492 	if(startedfs)
2493 		unmount(nil, "/mail/fs");
2494 	chdir("/sys/src/cmd/upas/ned");		/* for profiling? */
2495 	exits(rv);
2496 }
2497