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