Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 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
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
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 *
201
plural(int n)
202
{
203
	if (n == 1)
204
		return "";
205
 
206
	return "s";		
207
}
208
 
209
void
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*
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
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
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*
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*
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
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*
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
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
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*
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
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
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*
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
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
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
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*
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 *
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*
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
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*
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*
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
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*
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*
1279
Hcmd(Cmd*, Message *m)
1280
{
1281
	if(m == &top)
1282
		return nil;
1283
	aichcmd(m, 0);
1284
	return nil;
1285
}
1286
 
1287
Message*
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*
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*
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
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
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
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*
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
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*
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
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*
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
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*
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*
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*
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*
1688
ycmd(Cmd*, Message *m)
1689
{
1690
	doflush = 1;
1691
 
1692
	return icmd(nil, m);
1693
}
1694
 
1695
Message*
1696
xcmd(Cmd*, Message*)
1697
{
1698
	exitfs(0);
1699
	return nil;	// not reached
1700
}
1701
 
1702
Message*
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*
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*
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*
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*
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
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
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 *
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*
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*
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*
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 *
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
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*
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*
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
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*
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*
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
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*
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*
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*
2366
pipecmd(Cmd *c, Message *m)
2367
{
2368
	return xpipecmd(c, m, "body");
2369
}
2370
 
2371
Message*
2372
rpipecmd(Cmd *c, Message *m)
2373
{
2374
	return xpipecmd(c, m, "rawunix");
2375
}
2376
 
2377
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
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
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*
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
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
2558
regerror(char*)
2559
{
2560
}
2561
 
2562
String*
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
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
}