xref: /inferno-os/appl/acme/acme/mail/src/Mailp.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
1implement mailpop3;
2
3include "sys.m";
4include "draw.m";
5include "bufio.m";
6include "daytime.m";
7include "sh.m";
8include "pop3.m";
9
10mailpop3 : module {
11	init : fn(ctxt : ref Draw->Context, argl : list of string);
12};
13
14sys : Sys;
15bufio : Bufio;
16daytime : Daytime;
17pop3 : Pop3;
18
19OREAD, OWRITE, ORDWR, NEWFD, FORKENV, FORKFD, NEWPGRP, UTFmax, EXCEPTION, ONCE : import Sys;
20FD, Dir, Exception : import sys;
21fprint, sprint, sleep, create, open, read, write, remove, stat, fstat, fwstat, fildes, pctl, pipe, dup, byte2char : import sys;
22Context : import Draw;
23EOF : import Bufio;
24Iobuf : import bufio;
25time : import daytime;
26
27DIRLEN : con 116;
28PNPROC, PNGROUP : con iota;
29False : con 0;
30True : con 1;
31EVENTSIZE : con 256;
32Runeself : con 16r80;
33OCEXEC : con 0;
34CHEXCL : con 0; # 16r20000000;
35CHAPPEND : con 0; # 16r40000000;
36
37Win : adt {
38	winid : int;
39	addr : ref FD;
40	body : ref Iobuf;
41	ctl : ref FD;
42	data : ref FD;
43	event : ref FD;
44	buf : array of byte;
45	bufp : int;
46	nbuf : int;
47
48	wnew : fn() : ref Win;
49	wwritebody : fn(w : self ref Win, s : string);
50	wread : fn(w : self ref Win, m : int, n : int) : string;
51	wclean : fn(w : self ref Win);
52	wname : fn(w : self ref Win, s : string);
53	wdormant : fn(w : self ref Win);
54	wevent : fn(w : self ref Win, e : ref Event);
55	wshow : fn(w : self ref Win);
56	wtagwrite : fn(w : self ref Win, s : string);
57	wwriteevent : fn(w : self ref Win, e : ref Event);
58	wslave : fn(w : self ref Win, c : chan of Event);
59	wreplace : fn(w : self ref Win, s : string, t : string);
60	wselect : fn(w : self ref Win, s : string);
61	wsetdump : fn(w : self ref Win, s : string, t : string);
62	wdel : fn(w : self ref Win, n : int) : int;
63	wreadall : fn(w : self ref Win) : string;
64
65 	ctlwrite : fn(w : self ref Win, s : string);
66 	getec : fn(w : self ref Win) : int;
67 	geten : fn(w : self ref Win) : int;
68 	geter : fn(w : self ref Win, s : array of byte) : (int, int);
69 	openfile : fn(w : self ref Win, s : string) : ref FD;
70 	openbody : fn(w : self ref Win, n : int);
71};
72
73Mesg : adt {
74	w : ref Win;
75	id : int;
76	popno : int;
77	hdr : string;
78	realhdr : string;
79	replyto : string;
80	text : string;
81	subj : string;
82	next : cyclic ref Mesg;
83 	lline1 : int;
84	box : cyclic ref Box;
85	isopen : int;
86	posted : int;
87	deleted : int;
88
89	read : fn(b : ref Box) : ref Mesg;
90	open : fn(m : self ref Mesg);
91	slave : fn(m : self ref Mesg);
92	free : fn(m : self ref Mesg);
93	save : fn(m : self ref Mesg, s : string);
94	mkreply : fn(m : self ref Mesg);
95	mkmail : fn(b : ref Box, s : string);
96	putpost : fn(m : self ref Mesg, e : ref Event);
97
98 	command : fn(m : self ref Mesg, s : string) : int;
99 	send : fn(m : self ref Mesg);
100};
101
102Box : adt {
103	w : ref Win;
104	nm : int;
105	readonly : int;
106	m : cyclic ref Mesg;
107#	io : ref Iobuf;
108	clean : int;
109 	leng : int;
110 	cdel : chan of ref Mesg;
111	cevent : chan of Event;
112	cmore : chan of int;
113	lst : list of int;
114	s : string;
115
116	line : string;
117	popno : int;
118	peekline : string;
119
120	read : fn(n : int) : ref Box;
121	readmore : fn(b : self ref Box);
122	readline : fn(b : self ref Box) : string;
123	unreadline : fn(b : self ref Box);
124	slave : fn(b : self ref Box);
125	mopen : fn(b : self ref Box, n : int);
126	rewrite : fn(b : self ref Box);
127	mdel : fn(b : self ref Box, m : ref Mesg);
128	event : fn(b : self ref Box, e : ref Event);
129
130	command : fn(b : self ref Box, s : string) : int;
131};
132
133Event : adt {
134	c1 : int;
135	c2 : int;
136	q0 : int;
137	q1 : int;
138	flag : int;
139	nb : int;
140	nr : int;
141	b : array of byte;
142	r : array of int;
143};
144
145Lock : adt {
146	cnt : int;
147	chann : chan of int;
148
149	init : fn() : ref Lock;
150	lock : fn(l : self ref Lock);
151	unlock : fn(l : self ref Lock);
152};
153
154Ref : adt {
155	l : ref Lock;
156	cnt : int;
157
158	init : fn() : ref Ref;
159	inc : fn(r : self ref Ref) : int;
160};
161
162mbox : ref Box;
163user : string;
164date : string;
165mailctxt : ref Context;
166stdout, stderr : ref FD;
167
168killing : int = 0;
169
170init(ctxt : ref Context, argl : list of string)
171{
172	mailctxt = ctxt;
173	sys = load Sys Sys->PATH;
174	bufio = load Bufio Bufio->PATH;
175	daytime = load Daytime Daytime->PATH;
176	pop3 = load Pop3 Pop3->PATH;
177	stdout = fildes(1);
178	stderr = fildes(2);
179	main();
180}
181
182dlock : ref Lock;
183dfd : ref Sys->FD;
184
185debug(s : string)
186{
187	if (dfd == nil) {
188		dfd = sys->create("/usr/jrf/acme/debugmail", Sys->OWRITE, 8r600);
189		dlock = Lock.init();
190	}
191	if (dfd == nil)
192		return;
193	dlock.lock();
194	sys->fprint(dfd, "%s", s);
195	dlock.unlock();
196}
197
198postnote(t : int, pid : int, note : string) : int
199{
200	fd := open("#p/" + string pid + "/ctl", OWRITE);
201	if (fd == nil)
202		return -1;
203	if (t == PNGROUP)
204		note += "grp";
205	fprint(fd, "%s", note);
206	fd = nil;
207	return 0;
208}
209
210exec(cmd : string, argl : list of string)
211{
212	file := cmd;
213	if(len file<4 || file[len file-4:]!=".dis")
214		file += ".dis";
215
216	c := load Command file;
217	if(c == nil) {
218		err := sprint("%r");
219		if(file[0]!='/' && file[0:2]!="./"){
220			c = load Command "/dis/"+file;
221			if(c == nil)
222				err = sprint("%r");
223		}
224		if(c == nil){
225			fprint(stderr, "%s: %s\n", cmd, err);
226			return;
227		}
228	}
229	c->init(mailctxt, argl);
230}
231
232swrite(fd : ref FD, s : string) : int
233{
234	ab := array of byte s;
235	m := len ab;
236	p := write(fd, ab, m);
237	if (p == m)
238		return len s;
239	if (p <= 0)
240		return p;
241	return 0;
242}
243
244strchr(s : string, c : int) : int
245{
246	for (i := 0; i < len s; i++)
247		if (s[i] == c)
248			return i;
249	return -1;
250}
251
252strrchr(s : string, c : int) : int
253{
254	for (i := len s - 1; i >= 0; i--)
255		if (s[i] == c)
256			return i;
257	return -1;
258}
259
260strtoi(s : string) : (int, int)
261{
262	m := 0;
263	neg := 0;
264	t := 0;
265	ls := len s;
266	while (t < ls && (s[t] == ' ' || s[t] == '\t'))
267		t++;
268	if (t < ls && s[t] == '+')
269		t++;
270	else if (t < ls && s[t] == '-') {
271		neg = 1;
272		t++;
273	}
274	while (t < ls && (s[t] >= '0' && s[t] <= '9')) {
275		m = 10*m + s[t]-'0';
276		t++;
277	}
278	if (neg)
279		m = -m;
280	return (m, t);
281}
282
283access(s : string) : int
284{
285	fd := open(s, 0);
286	if (fd == nil)
287		return -1;
288	fd = nil;
289	return 0;
290}
291
292newevent() : ref Event
293{
294	e := ref Event;
295	e.b = array[EVENTSIZE*UTFmax+1] of byte;
296	e.r = array[EVENTSIZE+1] of int;
297	return e;
298}
299
300newmesg() : ref Mesg
301{
302	m := ref Mesg;
303	m.id = m.lline1 = m.isopen = m.posted = m.deleted = 0;
304	return m;
305}
306
307lc, uc : chan of ref Lock;
308
309initlock()
310{
311	lc = chan of ref Lock;
312	uc = chan of ref Lock;
313	spawn lockmgr();
314}
315
316lockmgr()
317{
318	l : ref Lock;
319
320	for (;;) {
321		alt {
322			l = <- lc =>
323				if (l.cnt++ == 0)
324					l.chann <-= 1;
325			l = <- uc =>
326				if (--l.cnt > 0)
327					l.chann <-= 1;
328		}
329	}
330}
331
332Lock.init() : ref Lock
333{
334	return ref Lock(0, chan of int);
335}
336
337Lock.lock(l : self ref Lock)
338{
339	lc <-= l;
340	<- l.chann;
341}
342
343Lock.unlock(l : self ref Lock)
344{
345	uc <-= l;
346}
347
348Ref.init() : ref Ref
349{
350	r := ref Ref;
351	r.l = Lock.init();
352	r.cnt = 0;
353	return r;
354}
355
356Ref.inc(r : self ref Ref) : int
357{
358	r.l.lock();
359	i := r.cnt;
360	r.cnt++;
361	r.l.unlock();
362	return i;
363}
364
365error(s : string)
366{
367	if(s != nil)
368		fprint(stderr, "mail: %s\n", s);
369	postnote(PNGROUP, pctl(0, nil), "kill");
370	killing = 1;
371	exit;
372}
373
374tryopen(s : string, mode : int) : ref FD
375{
376	fd : ref FD;
377	try : int;
378
379	for(try=0; try<3; try++){
380		fd = open(s, mode);
381		if(fd != nil)
382			return fd;
383		sleep(1000);
384	}
385	return nil;
386}
387
388run(argv : list of string, c : chan of int, p0 : ref FD)
389{
390	# pctl(FORKFD|NEWPGRP, nil);	# had RFMEM
391	pctl(FORKENV|NEWFD|NEWPGRP, 0::1::2::p0.fd::nil);
392	c <-= pctl(0, nil);
393	dup(p0.fd, 0);
394	p0 = nil;
395	exec(hd argv, argv);
396	exit;
397}
398
399getuser() : string
400{
401  	fd := open("/dev/user", OREAD);
402  	if(fd == nil)
403    		return "";
404  	buf := array[128] of byte;
405  	n := read(fd, buf, len buf);
406  	if(n < 0)
407    		return "";
408  	return string buf[0:n];
409}
410
411pop3conn : int = 0;
412pop3bad : int = 0;
413pop3lock : ref Lock;
414
415pop3open()
416{
417	pop3lock.lock();
418	if (!pop3conn) {
419		(ok, s) := pop3->open(user, "********", nil);	# password now got from user in Mailpop3.b
420		if (ok < 0) {
421			if (!pop3bad) {
422				fprint(stderr, "mail: could not connect to POP3 mail server : %s\n", s);
423				pop3bad = 1;
424			}
425			return;
426		}
427	}
428	pop3conn = 1;
429	pop3bad = 0;
430}
431
432pop3close()
433{
434	if (pop3conn) {
435		(ok, s) := pop3->close();
436		if (ok < 0) {
437			fprint(stderr, "mail: could not close POP3 connection : %s\n", s);
438			pop3lock.unlock();
439			return;
440		}
441	}
442	pop3conn = 0;
443	pop3lock.unlock();
444}
445
446pop3stat(b : ref Box) : int
447{
448	(ok, s, nm, nil) := pop3->stat();
449	if (ok < 0 && pop3conn) {
450		fprint(stderr, "mail: could not stat POP3 server : %s\n", s);
451		return b.leng;
452	}
453	return nm;
454}
455
456pop3list() : list of int
457{
458	(ok, s, l) := pop3->msgnolist();
459	if (ok < 0 && pop3conn) {
460		fprint(stderr, "mail: could not get list from POP3 server : %s\n", s);
461		return nil;
462	}
463	return l;
464}
465
466pop3mesg(mno : int) : string
467{
468	(ok, s, msg) := pop3->get(mno);
469	if (ok < 0 && pop3conn) {
470		fprint(stderr, "mail: could not retrieve a message from server : %s\n", s);
471		return "Acme Mail : FAILED TO RETRIEVE MESSAGE\n";
472	}
473	return msg;
474}
475
476pop3del(mno : int) : int
477{
478	(ok, s) := pop3->delete(mno);
479	if (ok < 0)
480		fprint(stderr, "mail: could not delete message : %s\n", s);
481	return ok;
482}
483
484pop3init(b : ref Box)
485{
486	b.leng = pop3stat(b);
487	b.lst = pop3list();
488	b.s = nil;
489	b.popno = 0;
490}
491
492pop3more(b : ref Box)
493{
494	nl : list of int;
495
496	leng := b.leng;
497	b.leng = pop3stat(b);
498	b.lst = pop3list();
499	b.s = nil;
500	b.popno = 0;
501	if (len b.lst != b.leng || b.leng <= leng)
502		error("bad lengths in pop3more()");
503	# is this ok ?
504	nl = nil;
505	for (i := 0; i < leng; i++) {
506		nl = hd b.lst :: nl;
507		b.lst = tl b.lst;
508	}
509	# now update pop nos.
510	for (m := b.m; m != nil; m = m.next) {
511		# opopno := m.popno;
512		if (nl == nil)
513			error("message list too big");
514		m.popno = hd nl;
515		nl = tl nl;
516		# debug(sys->sprint("%d : popno from %d to %d\n", m.id, opopno, m.popno));
517	}
518	if (nl != nil)
519		error("message list too small");
520}
521
522pop3next(b : ref Box) : string
523{
524	mno : int = 0;
525	r : string;
526
527	if (b.s == nil) {
528		if (b.lst == nil)
529			return nil;	# end of box
530		first := b.popno == 0;
531		mno = hd b.lst;
532		b.lst = tl b.lst;
533		b.s = pop3mesg(mno);
534		b.popno = mno;
535		if (!first)
536			return nil;	# end of message
537	}
538	t := strchr(b.s, '\n');
539	if (t >= 0) {
540		r = b.s[0:t+1];
541		b.s = b.s[t+1:];
542	}
543	else {
544		r = b.s;
545		b.s = nil;
546	}
547	return r;
548}
549
550main()
551{
552	readonly : int;
553
554	initlock();
555	initreply();
556	date = time();
557	if(date==nil)
558		error("can't get current time");
559	user = getuser();
560	if(user == nil)
561		user = "Wile.E.Coyote";
562	readonly = False;
563	pop3lock = Lock.init();
564	mbox = mbox.read(readonly);
565	spawn timeslave(mbox, mbox.cmore);
566	mbox.slave();
567	error(nil);
568}
569
570timeslave(b : ref Box, c : chan of int)
571{
572	for(;;){
573		sleep(30*1000);
574		pop3open();
575		leng := pop3stat(b);
576		pop3close();
577		if (leng > b.leng)
578			c <-= 0;
579	}
580}
581
582Win.wnew() : ref Win
583{
584	w := ref Win;
585	buf := array[12] of byte;
586	w.ctl = open("/chan/new/ctl", ORDWR);
587	if(w.ctl==nil || read(w.ctl, buf, 12)!=12)
588		error("can't open window ctl file: %r");
589	w.ctlwrite("noscroll\n");
590	w.winid = int string buf;
591	w.event = w.openfile("event");
592	w.addr = nil;	# will be opened when needed
593	w.body = nil;
594	w.data = nil;
595	w.bufp = w.nbuf = 0;
596	w.buf = array[512] of byte;
597	return w;
598}
599
600Win.openfile(w : self ref Win, f : string) : ref FD
601{
602	buf := sprint("/chan/%d/%s", w.winid, f);
603	fd := open(buf, ORDWR|OCEXEC);
604	if(fd == nil)
605		error(sprint("can't open window %s file: %r", f));
606	return fd;
607}
608
609Win.openbody(w : self ref Win, mode : int)
610{
611	buf := sprint("/chan/%d/body", w.winid);
612	w.body = bufio->open(buf, mode|OCEXEC);
613	if(w.body == nil)
614		error("can't open window body file: %r");
615}
616
617Win.wwritebody(w : self ref Win, s : string)
618{
619	n := len s;
620	if(w.body == nil)
621		w.openbody(OWRITE);
622	if(w.body.puts(s) != n)
623		error("write error to window: %r");
624}
625
626Win.wreplace(w : self ref Win, addr : string, repl : string)
627{
628	if(w.addr == nil)
629		w.addr = w.openfile("addr");
630	if(w.data == nil)
631		w.data = w.openfile("data");
632	if(swrite(w.addr, addr) < 0){
633		fprint(stderr, "mail: warning: bad address %s:%r\n", addr);
634		return;
635	}
636	if(swrite(w.data, repl) != len repl)
637		error("writing data: %r");
638}
639
640nrunes(s : array of byte, nb : int) : int
641{
642	i, n : int;
643
644	n = 0;
645	for(i=0; i<nb; n++) {
646		(r, b, ok) := byte2char(s, i);
647		if (!ok)
648			error("help needed in nrunes()");
649		i += b;
650	}
651	return n;
652}
653
654Win.wread(w : self ref Win, q0 : int, q1 : int) : string
655{
656	m, n, nr : int;
657	s, buf : string;
658	b : array of byte;
659
660	b = array[256] of byte;
661	if(w.addr == nil)
662		w.addr = w.openfile("addr");
663	if(w.data == nil)
664		w.data = w.openfile("data");
665	s = nil;
666	m = q0;
667	while(m < q1){
668		buf = sprint("#%d", m);
669		if(swrite(w.addr, buf) != len buf)
670			error("writing addr: %r");
671		n = read(w.data, b, len b);
672		if(n <= 0)
673			error("reading data: %r");
674		nr = nrunes(b, n);
675		while(m+nr >q1){
676			do; while(n>0 && (int b[--n]&16rC0)==16r80);
677			--nr;
678		}
679		if(n == 0)
680			break;
681		s += string b[0:n];
682		m += nr;
683	}
684	return s;
685}
686
687Win.wshow(w : self ref Win)
688{
689	w.ctlwrite("show\n");
690}
691
692Win.wsetdump(w : self ref Win, dir : string, cmd : string)
693{
694	t : string;
695
696	if(dir != nil){
697		t = "dumpdir " + dir + "\n";
698		w.ctlwrite(t);
699		t = nil;
700	}
701	if(cmd != nil){
702		t = "dump " + cmd + "\n";
703		w.ctlwrite(t);
704		t = nil;
705	}
706}
707
708Win.wselect(w : self ref Win, addr : string)
709{
710	if(w.addr == nil)
711		w.addr = w.openfile("addr");
712	if(swrite(w.addr, addr) < 0)
713		error("writing addr");
714	w.ctlwrite("dot=addr\n");
715}
716
717Win.wtagwrite(w : self ref Win, s : string)
718{
719	fd : ref FD;
720
721	fd = w.openfile("tag");
722	if(swrite(fd, s) != len s)
723		error("tag write: %r");
724	fd = nil;
725}
726
727Win.ctlwrite(w : self ref Win, s : string)
728{
729	if(swrite(w.ctl, s) != len s)
730		error("write error to ctl file: %r");
731}
732
733Win.wdel(w : self ref Win, sure : int) : int
734{
735	if (w == nil)
736		return False;
737	if(sure)
738		swrite(w.ctl, "delete\n");
739	else if(swrite(w.ctl, "del\n") != 4)
740		return False;
741	w.wdormant();
742	w.ctl = nil;
743	w.event = nil;
744	return True;
745}
746
747Win.wname(w : self ref Win, s : string)
748{
749	w.ctlwrite("name " + s + "\n");
750}
751
752Win.wclean(w : self ref Win)
753{
754	if(w.body != nil)
755		w.body.flush();
756	w.ctlwrite("clean\n");
757}
758
759Win.wdormant(w : self ref Win)
760{
761	w.addr = nil;
762	if(w.body != nil){
763		w.body.close();
764		w.body = nil;
765	}
766	w.data = nil;
767}
768
769Win.getec(w : self ref Win) : int
770{
771	if(w.nbuf == 0){
772		w.nbuf = read(w.event, w.buf, len w.buf);
773		if(w.nbuf <= 0 && !killing) {
774			error("event read error: %r");
775		}
776		w.bufp = 0;
777	}
778	w.nbuf--;
779	return int w.buf[w.bufp++];
780}
781
782Win.geten(w : self ref Win) : int
783{
784	n, c : int;
785
786	n = 0;
787	while('0'<=(c=w.getec()) && c<='9')
788		n = n*10+(c-'0');
789	if(c != ' ')
790		error("event number syntax");
791	return n;
792}
793
794Win.geter(w : self ref Win, buf : array of byte) : (int, int)
795{
796	r, m, n, ok : int;
797
798	r = w.getec();
799	buf[0] = byte r;
800	n = 1;
801	if(r >= Runeself) {
802		for (;;) {
803			(r, m, ok) = byte2char(buf[0:n], 0);
804			if (m > 0)
805				return (r, n);
806			buf[n++] = byte w.getec();
807		}
808	}
809	return (r, n);
810}
811
812Win.wevent(w : self ref Win, e : ref Event)
813{
814	i, nb : int;
815
816	e.c1 = w.getec();
817	e.c2 = w.getec();
818	e.q0 = w.geten();
819	e.q1 = w.geten();
820	e.flag = w.geten();
821	e.nr = w.geten();
822	if(e.nr > EVENTSIZE)
823		error("event string too long");
824	e.nb = 0;
825	for(i=0; i<e.nr; i++){
826		(e.r[i], nb) = w.geter(e.b[e.nb:]);
827		e.nb += nb;
828	}
829	e.r[e.nr] = 0;
830	e.b[e.nb] = byte 0;
831	c := w.getec();
832	if(c != '\n')
833		error("event syntax 2");
834}
835
836Win.wslave(w : self ref Win, ce : chan of Event)
837{
838	e : ref Event;
839
840	e = newevent();
841	for(;;){
842		w.wevent(e);
843		ce <-= *e;
844	}
845}
846
847Win.wwriteevent(w : self ref Win, e : ref Event)
848{
849	fprint(w.event, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1);
850}
851
852Win.wreadall(w : self ref Win) : string
853{
854	s, t : string;
855
856	if(w.body != nil)
857		w.body.close();
858	w.openbody(OREAD);
859	s = nil;
860	while ((t = w.body.gets('\n')) != nil)
861		s += t;
862	w.body.close();
863	w.body = nil;
864	return s;
865}
866
867None,Unknown,Ignore,CC,From,ReplyTo,Sender,Subject,Re,To, Date : con iota;
868NHeaders : con 200;
869
870Hdrs : adt {
871	name : string;
872	typex : int;
873};
874
875
876hdrs := array[NHeaders+1] of {
877	Hdrs ( "CC:",				CC ),
878	Hdrs ( "From:",				From ),
879	Hdrs ( "Reply-To:",			ReplyTo ),
880	Hdrs ( "Sender:",			Sender ),
881	Hdrs ( "Subject:",			Subject ),
882	Hdrs ( "Re:",				Re ),
883	Hdrs ( "To:",				To ),
884	Hdrs ( "Date:",				Date),
885 * => Hdrs ( "",					0 ),
886};
887
888StRnCmP(s : string, t : string, n : int) : int
889{
890	c, d, i, j : int;
891
892	i = j = 0;
893	if (len s < n || len t < n)
894		return -1;
895	while(n > 0){
896		c = s[i++];
897		d = t[j++];
898		--n;
899		if(c != d){
900			if('a'<=c && c<='z')
901				c -= 'a'-'A';
902			if('a'<=d && d<='z')
903				d -= 'a'-'A';
904			if(c != d)
905				return c-d;
906		}
907	}
908	return 0;
909}
910
911readhdr(b : ref Box) : (string, int)
912{
913	i, j, n, m, typex : int;
914	s, t : string;
915
916{
917	s = b.readline();
918	n = len s;
919	if(n <= 0) {
920		b.unreadline();
921		raise("e");
922	}
923	for(i=0; i<n; i++){
924		j = s[i];
925		if(i>0 && j == ':')
926			break;
927		if(j<'!' || '~'<j){
928			b.unreadline();
929			raise("e");
930		}
931	}
932	typex = Unknown;
933	for(i=0; hdrs[i].name != nil; i++){
934		j = len hdrs[i].name;
935		if(StRnCmP(hdrs[i].name, s, j) == 0){
936			typex = hdrs[i].typex;
937			break;
938		}
939	}
940	# scan for multiple sublines
941	for(;;){
942		t = b.readline();
943		m = len t;
944		if(m<=0 || (t[0]!=' ' && t[0]!='\t')){
945			b.unreadline();
946			break;
947		}
948		# absorb
949		s += t;
950	}
951	return(s, typex);
952}
953exception{
954	"*" =>
955		return (nil, None);
956}
957}
958
959Mesg.read(b : ref Box) : ref Mesg
960{
961	m : ref Mesg;
962	s : string;
963	n, typex : int;
964
965	s = b.readline();
966	n = len s;
967	if(n <= 0)
968		return nil;
969
970{
971	if(n < 5 || (s[0:5] !="From " && s[0:5] != "From:"))
972		raise("e");
973	m = newmesg();
974	m.popno = b.popno;
975	if (m.popno == 0)
976		error("bad pop3 id");
977	m.realhdr = s;
978	# toss 'From '
979	s = s[5:];
980	n -= 5;
981	# toss spaces/tabs
982	while (n > 0 && (s[0] == ' ' || s[0] == '\t')) {
983		s = s[1:];
984		n--;
985	}
986	m.hdr = s;
987	# convert first blank to tab
988	s0 := strchr(m.hdr, ' ');
989	if(s0 >= 0){
990		m.hdr[s0] = '\t';
991		# drop trailing seconds, time zone, and year if match local year
992		t := n-6;
993		if(t <= 0)
994			raise("e");
995		if(m.hdr[t:n-1] == date[23:]){
996			m.hdr = m.hdr[0:t] + "\n";	# drop year for sure
997			t = -1;
998			s1 := strchr(m.hdr[s0:], ':');
999			if(s1 >= 0)
1000				t = strchr(m.hdr[s0+s1+1:], ':');
1001			if(t >= 0)	# drop seconds and time zone
1002				m.hdr = m.hdr[0:s0+s1+t+1] + "\n";
1003			else{	# drop time zone
1004				t = strchr(m.hdr[s0+s1+1:], ' ');
1005				if(t >= 0)
1006					m.hdr = m.hdr[0:s0+s1+t+1] + "\n";
1007			}
1008			n = len m.hdr;
1009		}
1010	}
1011	m.lline1 = n;
1012	m.text = nil;
1013	# read header
1014loop:
1015	for(;;){
1016		(s, typex) = readhdr(b);
1017		case(typex){
1018		None =>
1019			break loop;
1020		ReplyTo =>
1021			m.replyto = s[9:];
1022			break;
1023		From =>
1024			if(m.replyto == nil)
1025				m.replyto = s[5:];
1026			break;
1027		Subject =>
1028			m.subj = s[8:];
1029			break;
1030		Re =>
1031			m.subj = s[3:];
1032			break;
1033		Date =>
1034			break;
1035		}
1036		m.realhdr += s;
1037		if(typex != Ignore)
1038			m.hdr += s;
1039	}
1040	# read body
1041	for(;;){
1042		s = b.readline();
1043		n = len s;
1044		if(n <= 0)
1045			break;
1046#		if(len s >= 5 && (s[0:5] == "From " || s[0:5] == "From:")){
1047#			b.unreadline();
1048#			break;
1049#		}
1050		m.text += s;
1051	}
1052	# remove trailing "morF\n"
1053	l := len m.text;
1054	if(l>6 && m.text[l-6:] == "\nmorF\n")
1055		m.text = m.text[0:l-5];
1056	m.box = b;
1057	return m;
1058}
1059exception{
1060	"*" =>
1061		error("malformed header " + s);
1062		return nil;
1063}
1064}
1065
1066Mesg.mkmail(b : ref Box, hdr : string)
1067{
1068	r : ref Mesg;
1069
1070	r = newmesg();
1071	r.hdr = hdr + "\n";
1072	r.lline1 = len r.hdr;
1073	r.text = nil;
1074	r.box = b;
1075	r.open();
1076	r.w.wdormant();
1077}
1078
1079replyaddr(r : string) : string
1080{
1081	p, q, rr : int;
1082
1083	rr = 0;
1084	while(r[rr]==' ' || r[rr]=='\t')
1085		rr++;
1086	r = r[rr:];
1087	p = strchr(r, '<');
1088	if(p >= 0){
1089		q = strchr(r[p+1:], '>');
1090		if(q < 0)
1091			r = r[p+1:];
1092		else
1093			r = r[p+1:p+q] + "\n";
1094		return r;
1095	}
1096	p = strchr(r, '(');
1097	if(p >= 0){
1098		q = strchr(r[p:], ')');
1099		if(q < 0)
1100			r = r[0:p];
1101		else
1102			r = r[0:p] + r[p+q+1:];
1103	}
1104	return r;
1105}
1106
1107Mesg.mkreply(m : self ref Mesg)
1108{
1109	r : ref Mesg;
1110
1111	r = newmesg();
1112	if(m.replyto != nil){
1113		r.hdr = replyaddr(m.replyto);
1114		r.lline1 = len r.hdr;
1115	}else{
1116		r.hdr = m.hdr[0:m.lline1];
1117		r.lline1 = m.lline1;	# was len m.hdr;
1118	}
1119	if(m.subj != nil){
1120		if(StRnCmP(m.subj, "re:", 3)==0 || StRnCmP(m.subj, " re:", 4)==0)
1121			r.text = "Subject:" + m.subj + "\n";
1122		else
1123			r.text = "Subject: Re:" + m.subj + "\n";
1124	}
1125	else
1126		r.text = nil;
1127	r.box = m.box;
1128	r.open();
1129	r.w.wselect("$");
1130	r.w.wdormant();
1131}
1132
1133Mesg.free(m : self ref Mesg)
1134{
1135	m.text = nil;
1136	m.hdr = nil;
1137	m.subj = nil;
1138	m.realhdr = nil;
1139	m.replyto = nil;
1140	m = nil;
1141}
1142
1143replyid : ref Ref;
1144
1145initreply()
1146{
1147	replyid = Ref.init();
1148}
1149
1150Mesg.open(m : self ref Mesg)
1151{
1152	buf, s : string;
1153
1154	if(m.isopen)
1155		return;
1156	m.w = Win.wnew();
1157	if(m.id != 0)
1158		m.w.wwritebody("From ");
1159	m.w.wwritebody(m.hdr);
1160	m.w.wwritebody(m.text);
1161	if(m.id){
1162		buf = sprint("Mail/box/%d", m.id);
1163		m.w.wtagwrite("Reply Delmesg Save");
1164	}else{
1165		buf = sprint("Mail/%s/Reply%d", s, replyid.inc());
1166		m.w.wtagwrite("Post");
1167	}
1168	m.w.wname(buf);
1169	m.w.wclean();
1170	m.w.wselect("0");
1171	m.isopen = True;
1172	m.posted = False;
1173	spawn m.slave();
1174}
1175
1176Mesg.putpost(m : self ref Mesg, e : ref Event)
1177{
1178	if(m.posted || m.id==0)
1179		return;
1180	if(e.q0 >= len m.hdr+5)	# include "From "
1181		return;
1182	m.w.wtagwrite(" Post");
1183	m.posted = True;
1184	return;
1185}
1186
1187Mesg.slave(m : self ref Mesg)
1188{
1189	e, e2, ea, etoss, eq : ref Event;
1190	s : string;
1191	na : int;
1192
1193	e = newevent();
1194	e2 = newevent();
1195	ea = newevent();
1196	etoss = newevent();
1197	for(;;){
1198		m.w.wevent(e);
1199		case(e.c1){
1200		'E' =>	# write to body; can't affect us
1201			break;
1202		'F' =>	# generated by our actions; ignore
1203			break;
1204		'K' or 'M' =>	# type away; we don't care
1205			case(e.c2){
1206			'x' or 'X' =>	# mouse only
1207				eq = e;
1208				if(e.flag & 2){
1209					m.w.wevent(e2);
1210					eq = e2;
1211				}
1212				if(e.flag & 8){
1213					m.w.wevent(ea);
1214					m.w.wevent(etoss);
1215					na = ea.nb;
1216				}else
1217					na = 0;
1218				if(eq.q1>eq.q0 && eq.nb==0)
1219					s = m.w.wread(eq.q0, eq.q1);
1220				else
1221					s = string eq.b[0:eq.nb];
1222				if(na)
1223					s = s + " " + string ea.b[0:ea.nb];
1224				if(!m.command(s))	# send it back
1225					m.w.wwriteevent(e);
1226				s = nil;
1227				break;
1228			'l' or 'L' =>	# mouse only
1229				if(e.flag & 2)
1230					m.w.wevent(e2);
1231				# just send it back
1232				m.w.wwriteevent(e);
1233				break;
1234			'I' or 'D' =>	# modify away; we don't care
1235				m.putpost(e);
1236				break;
1237			'd' or 'i' =>
1238				break;
1239			* =>
1240				fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
1241				break;
1242			}
1243		* =>
1244			fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
1245			break;
1246		}
1247	}
1248}
1249
1250Mesg.command(m : self ref Mesg, s : string) : int
1251{
1252	while(s[0]==' ' || s[0]=='\t' || s[0]=='\n')
1253		s = s[1:];
1254	if(s == "Post"){
1255		m.send();
1256		return True;
1257	}
1258	if(len s >= 4 && s[0:4] == "Save"){
1259		s = s[4:];
1260		while(s[0]==' ' || s[0]=='\t' || s[0]=='\n')
1261			s = s[1:];
1262		if(s == nil)
1263			m.save("stored");
1264		else{
1265			ss := 0;
1266			while(ss < len s && s[ss]!=' ' && s[ss]!='\t' && s[ss]!='\n')
1267				ss++;
1268			m.save(s[0:ss]);
1269		}
1270		return True;
1271	}
1272	if(s == "Reply"){
1273		m.mkreply();
1274		return True;
1275	}
1276	if(s == "Del"){
1277		if(m.w.wdel(False)){
1278			m.isopen = False;
1279			exit;
1280		}
1281		return True;
1282	}
1283	if(s == "Delmesg"){
1284		if(m.w.wdel(False)){
1285			m.isopen = False;
1286			m.box.cdel <-= m;
1287			exit;
1288		}
1289		return True;
1290	}
1291	return False;
1292}
1293
1294Mesg.save(m : self ref Mesg, base : string)
1295{
1296	s, buf : string;
1297	n : int;
1298	fd : ref FD;
1299	b : ref Iobuf;
1300
1301	if(m.id <= 0){
1302		fprint(stderr, "can't save reply message; mail it to yourself\n");
1303		return;
1304	}
1305	buf = nil;
1306	s = base;
1307{
1308	if(access(s) < 0)
1309		raise("e");
1310	fd = tryopen(s, OWRITE);
1311	if(fd == nil)
1312		raise("e");
1313	buf = nil;
1314	b = bufio->fopen(fd, OWRITE);
1315	# seek to end in case file isn't append-only
1316	b.seek(big 0, 2);
1317	# use edited headers: first line of real header followed by remainder of selected ones
1318	for(n=0; n<len m.realhdr && m.realhdr[n++]!='\n'; )
1319		;
1320	b.puts(m.realhdr[0:n]);
1321	b.puts(m.hdr[m.lline1:]);
1322	b.puts(m.text);
1323	b.close();
1324	b = nil;
1325	fd = nil;
1326}
1327exception{
1328	"*" =>
1329		buf = nil;
1330		fprint(stderr, "mail: can't open %s: %r\n", base);
1331		return;
1332}
1333}
1334
1335Mesg.send(m : self ref Mesg)
1336{
1337	s, buf : string;
1338	t, u : int;
1339	a, b : list of string;
1340	n : int;
1341	p : array of ref FD;
1342	c : chan of int;
1343
1344	p = array[2] of ref FD;
1345	s = m.w.wreadall();
1346	a = "sendmail" :: nil;
1347	if(len s >= 5 && (s[0:5] == "From " || s[0:5] == "From:"))
1348		s = s[5:];
1349	for(t=0; t < len s && s[t]!='\n' && s[t]!='\t';){
1350		while(t < len s && (s[t]==' ' || s[t]==','))
1351			t++;
1352		u = t;
1353		while(t < len s && s[t]!=' ' && s[t]!=',' && s[t]!='\t' && s[t]!='\n')
1354			t++;
1355		if(t == u)
1356			break;
1357		a = s[u:t] :: a;
1358	}
1359	b = nil;
1360	for ( ; a != nil; a = tl a)
1361		b = hd a :: b;
1362	a = b;
1363	while(t < len s && s[t]!='\n')
1364		t++;
1365	if(s[t] == '\n')
1366		t++;
1367	if(pipe(p) < 0)
1368		error("can't pipe: %r");
1369	c = chan of int;
1370	spawn run(a, c, p[0]);
1371	<-c;
1372	c = nil;
1373	p[0] = nil;
1374	n = len s - t;
1375	if(swrite(p[1], s[t:]) != n)
1376		fprint(stderr, "write to pipe failed: %r\n");
1377	p[1] = nil;
1378	# run() frees the arg list
1379	buf = sprint("Mail/box/%d-R", m.id);
1380	m.w.wname(buf);
1381	m.w.wclean();
1382}
1383
1384Box.read(readonly : int) : ref Box
1385{
1386	b : ref Box;
1387	m : ref Mesg;
1388	buf : string;
1389
1390	b = ref Box;
1391	b.nm = 0;
1392	b.leng = 0;
1393	b.readonly = readonly;
1394	pop3open();
1395	pop3init(b);
1396	while((m = m.read(b)) != nil){
1397		m.next = b.m;
1398		b.m = m;
1399		b.nm++;
1400		m.id = b.nm;
1401	}
1402	pop3close();
1403	if (b.leng != b.nm)
1404		error("bad message count in Box.read()");
1405	b.w = Win.wnew();
1406	for(m=b.m; m != nil; m=m.next){
1407		if(m.subj != nil)
1408			buf = sprint("%d\t%s\t %s", m.id, m.hdr[0:m.lline1], m.subj);
1409		else
1410			buf = sprint("%d\t%s", m.id, m.hdr[0:m.lline1]);
1411		b.w.wwritebody(buf);
1412	}
1413	buf = sprint("Mail/box/");
1414	b.w.wname(buf);
1415	if(b.readonly)
1416		b.w.wtagwrite("Mail");
1417	else
1418		b.w.wtagwrite("Put Mail");
1419	buf = "Mail " + "box";
1420	b.w.wsetdump("/acme/mail", buf);
1421	b.w.wclean();
1422	b.w.wselect("0");
1423	b.w.wdormant();
1424	b.cdel= chan of ref Mesg;
1425	b.cevent = chan of Event;
1426	b.cmore = chan of int;
1427	spawn b.w.wslave(b.cevent);
1428	b.clean = True;
1429	return b;
1430}
1431
1432Box.readmore(b : self ref Box)
1433{
1434	m : ref Mesg;
1435	new : int;
1436	buf : string;
1437
1438	new = False;
1439	leng := b.leng;
1440	n := 0;
1441	pop3open();
1442	pop3more(b);
1443	while((m = m.read(b)) != nil){
1444		m.next = b.m;
1445		b.m = m;
1446		b.nm++;
1447		n++;
1448		m.id = b.nm;
1449		if(m.subj != nil)
1450			buf  = sprint("%d\t%s\t  %s", m.id, m.hdr[0:m.lline1], m.subj);
1451		else
1452			buf = sprint("%d\t%s", m.id, m.hdr[0:m.lline1]);
1453		b.w.wreplace("0", buf);
1454		new = True;
1455	}
1456	pop3close();
1457	if (b.leng != leng+n)
1458		error("bad message count in Box.readmore()");
1459	if(new){
1460		if(b.clean)
1461			b.w.wclean();
1462		b.w.wselect("0;/.*(\\n[ \t].*)*");
1463		b.w.wshow();
1464	}
1465	b.w.wdormant();
1466}
1467
1468Box.readline(b : self ref Box) : string
1469{
1470    	for (;;) {
1471		if(b.peekline != nil){
1472			b.line = b.peekline;
1473			b.peekline = nil;
1474		}else
1475			b.line = pop3next(b);
1476		# nulls appear in mailboxes!
1477		if(b.line != nil && strchr(b.line, 0) >= 0)
1478			;
1479		else
1480			break;
1481	}
1482	return b.line;
1483}
1484
1485Box.unreadline(b : self ref Box)
1486{
1487	b.peekline = b.line;
1488}
1489
1490Box.slave(b : self ref Box)
1491{
1492	e : ref Event;
1493	m : ref Mesg;
1494
1495	e = newevent();
1496	for(;;){
1497		alt{
1498		*e = <-b.cevent =>
1499			b.event(e);
1500			break;
1501		<-b.cmore =>
1502			b.readmore();
1503			break;
1504		m = <-b.cdel =>
1505			b.mdel(m);
1506			break;
1507		}
1508	}
1509}
1510
1511Box.event(b : self ref Box, e : ref Event)
1512{
1513	e2, ea, eq : ref Event;
1514	s : string;
1515	t : int;
1516	n, na, nopen : int;
1517
1518	e2 = newevent();
1519	ea = newevent();
1520	case(e.c1){
1521	'E' =>	# write to body; can't affect us
1522		break;
1523	'F' =>	# generated by our actions; ignore
1524		break;
1525	'K' =>	# type away; we don't care
1526		break;
1527	'M' =>
1528		case(e.c2){
1529		'x' or 'X' =>
1530			if(e.flag & 2)
1531				*e2 = <-b.cevent;
1532			if(e.flag & 8){
1533				*ea = <-b.cevent;
1534				na = ea.nb;
1535				<- b.cevent;
1536			}else
1537				na = 0;
1538			s = string e.b[0:e.nb];
1539			# if it's a known command, do it
1540			if((e.flag&2) && e.nb==0)
1541				s = string e2.b[0:e2.nb];
1542			if(na)
1543				s = sprint("%s %s", s, string ea.b[0:ea.nb]);
1544			# if it's a long message, it can't be for us anyway
1545			if(!b.command(s))	# send it back
1546				b.w.wwriteevent(e);
1547			if(na)
1548				s = nil;
1549			break;
1550		'l' or 'L' =>
1551			eq = e;
1552			if(e.flag & 2){
1553				*e2 = <-b.cevent;
1554				eq = e2;
1555			}
1556			s = string eq.b[0:eq.nb];
1557			if(eq.q1>eq.q0 && eq.nb==0)
1558				s = b.w.wread(eq.q0, eq.q1);
1559			nopen = 0;
1560			do{
1561				t = 0;
1562				(n, t) = strtoi(s);
1563				if(n>0 && (t == len s || s[t]==' ' || s[t]=='\t' || s[t]=='\n')){
1564					b.mopen(n);
1565					nopen++;
1566					s = s[t:];
1567				}
1568				while(s != nil && s[0]!='\n')
1569					s = s[1:];
1570			}while(s != nil);
1571			if(nopen == 0)	# send it back
1572				b.w.wwriteevent(e);
1573			break;
1574		'I' or 'D' or 'd' or 'i' =>	# modify away; we don't care
1575			break;
1576		* =>
1577			fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
1578			break;
1579		}
1580	* =>
1581		fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
1582		break;
1583	}
1584}
1585
1586Box.mopen(b : self ref Box, id : int)
1587{
1588	m : ref Mesg;
1589
1590	for(m=b.m; m != nil; m=m.next)
1591		if(m.id == id){
1592			m.open();
1593			break;
1594		}
1595}
1596
1597Box.mdel(b : self ref Box, dm : ref Mesg)
1598{
1599	m : ref Mesg;
1600	buf : string;
1601
1602	if(dm.id){
1603		for(m=b.m; m!=nil && m!=dm; m=m.next)
1604			;
1605		if(m == nil)
1606			error(sprint("message %d not found", dm.id));
1607		m.deleted = 1;
1608		# remove from screen: use acme to help
1609		buf = sprint("/^%d	.*\\n(^[ \t].*\\n)*/", m.id);
1610		b.w.wreplace(buf, "");
1611	}
1612	dm.free();
1613	b.clean = False;
1614}
1615
1616Box.command(b : self ref Box, s : string) : int
1617{
1618	t : int;
1619	m : ref Mesg;
1620
1621	while(s[0]==' ' || s[0]=='\t' || s[0]=='\n')
1622		s = s[1:];
1623	if(len s >= 4 && s[0:4] == "Mail"){
1624		s = s[4:];
1625		while(s != nil && (s[0]==' ' || s[0]=='\t' || s[0]=='\n'))
1626			s = s[1:];
1627		t = 0;
1628		while(t < len s && s[t] && s[t]!=' ' && s[t]!='\t' && s[t]!='\n')
1629			t++;
1630		m = b.m;		# avoid warning message on b.m.mkmail(...)
1631		m.mkmail(b, s[0:t]);
1632		return True;
1633	}
1634	if(s == "Del"){
1635
1636		if(!b.clean){
1637			b.clean = True;
1638			fprint(stderr, "mail: mailbox not written\n");
1639			return True;
1640		}
1641		postnote(PNGROUP, pctl(0, nil), "kill");
1642		killing = 1;
1643		pctl(NEWPGRP, nil);
1644		b.w.wdel(True);
1645		for(m=b.m; m != nil; m=m.next)
1646			m.w.wdel(False);
1647		exit;
1648		return True;
1649	}
1650	if(s == "Put"){
1651		if(b.readonly)
1652			fprint(stderr, "Mail is read-only\n");
1653		else
1654			b.rewrite();
1655		return True;
1656	}
1657	return False;
1658}
1659
1660Box.rewrite(b : self ref Box)
1661{
1662	prev, m : ref Mesg;
1663
1664	if(b.clean){
1665		b.w.wclean();
1666		return;
1667	}
1668	prev = nil;
1669	pop3open();
1670	for(m=b.m; m!=nil; m=m.next) {
1671		if (m.deleted && pop3del(m.popno) >= 0) {
1672			b.leng--;
1673			if (prev == nil)
1674				b.m=m.next;
1675			else
1676				prev.next=m.next;
1677		}
1678		else
1679			prev = m;
1680	}
1681	pop3close();
1682	b.w.wclean();
1683	b.clean = True;
1684}
1685