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