plan9port

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

nedmail.c (47272B)


      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
    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
    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 *
    210 plural(int n)
    211 {
    212 	if (n == 1)
    213 		return "";
    214 
    215 	return "s";
    216 }
    217 
    218 void
    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*
    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*
    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
    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
    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*
    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*
    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
    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*
    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
    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
    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*
    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
    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
    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*
    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
    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
    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
    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*
    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*
    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
   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*
   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*
   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
   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*
   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*
   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*
   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*
   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*
   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
   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
   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*
   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
   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*
   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
   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*
   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*
   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*
   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*
   1684 ycmd(Cmd *x, Message *m)
   1685 {
   1686 	USED(x);
   1687 
   1688 	doflush = 1;
   1689 
   1690 	return icmd(nil, m);
   1691 }
   1692 
   1693 Message*
   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*
   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*
   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*
   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*
   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*
   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
   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
   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*
   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*
   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*
   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 *
   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
   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*
   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*
   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
   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*
   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*
   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
   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*
   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*
   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*
   2404 pipecmd(Cmd *c, Message *m)
   2405 {
   2406 	return xpipecmd(c, m, "body");
   2407 }
   2408 
   2409 Message*
   2410 rpipecmd(Cmd *c, Message *m)
   2411 {
   2412 	return xpipecmd(c, m, "rawunix");
   2413 }
   2414 
   2415 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
   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
   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*
   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
   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
   2594 regerror(char *x)
   2595 {
   2596 	USED(x);
   2597 }
   2598 
   2599 String*
   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
   2613 exitfs(char *rv)
   2614 {
   2615 	threadexitsall(rv);
   2616 }