xref: /plan9/acme/news/src/news.c (revision 3ff48bf5ed603850fcd251ddf13025d23d693782)
1 /*
2  * Acme interface to nntpfs.
3  */
4 #include <u.h>
5 #include <libc.h>
6 #include <bio.h>
7 #include <thread.h>
8 #include "win.h"
9 #include <ctype.h>
10 
11 int canpost;
12 int debug;
13 int nshow = 20;
14 
15 int lo;	/* next message to look at in dir */
16 int hi;	/* current hi message in dir */
17 char *dir = "/mnt/news";
18 char *group;
19 char *from;
20 
21 typedef struct Article Article;
22 struct Article {
23 	Ref;
24 	Article *prev;
25 	Article *next;
26 	Window *w;
27 	int n;
28 	int dead;
29 	int dirtied;
30 	int sayspost;
31 	int headers;
32 	int ispost;
33 };
34 
35 Article *mlist;
36 Window *root;
37 
38 int
cistrncmp(char * a,char * b,int n)39 cistrncmp(char *a, char *b, int n)
40 {
41 	while(n-- > 0){
42 		if(tolower(*a++) != tolower(*b++))
43 			return -1;
44 	}
45 	return 0;
46 }
47 
48 int
cistrcmp(char * a,char * b)49 cistrcmp(char *a, char *b)
50 {
51 	for(;;){
52 		if(tolower(*a) != tolower(*b++))
53 			return -1;
54 		if(*a++ == 0)
55 			break;
56 	}
57 	return 0;
58 }
59 
60 char*
skipwhite(char * p)61 skipwhite(char *p)
62 {
63 	while(isspace(*p))
64 		p++;
65 	return p;
66 }
67 
68 int
gethi(void)69 gethi(void)
70 {
71 	Dir *d;
72 	int hi;
73 
74 	if((d = dirstat(dir)) == nil)
75 		return -1;
76 	hi = d->qid.vers;
77 	free(d);
78 	return hi;
79 }
80 
81 char*
fixfrom(char * s)82 fixfrom(char *s)
83 {
84 	char *r, *w;
85 
86 	s = estrdup(s);
87 	/* remove quotes */
88 	for(r=w=s; *r; r++)
89 		if(*r != '"')
90 			*w++ = *r;
91 	*w = '\0';
92 	return s;
93 }
94 
95 char *day[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", };
96 char *mon[] = {
97 	"Jan", "Feb", "Mar", "Apr",
98 	"May", "Jun", "Jul", "Aug",
99 	"Sep", "Oct", "Nov", "Dec",
100 };
101 
102 char*
fixdate(char * s)103 fixdate(char *s)
104 {
105 	char *f[10], *m, *t, *wd, tmp[40];
106 	int d, i, j, nf, hh, mm;
107 
108 	nf = tokenize(s, f, nelem(f));
109 
110 	wd = nil;
111 	d = 0;
112 	m = nil;
113 	t = nil;
114 	for(i=0; i<nf; i++){
115 		for(j=0; j<7; j++)
116 			if(cistrncmp(day[j], f[i], 3)==0)
117 				wd = day[j];
118 		for(j=0; j<12; j++)
119 			if(cistrncmp(mon[j], f[i], 3)==0)
120 				m = mon[j];
121 		j = atoi(f[i]);
122 		if(1 <= j && j <= 31 && d != 0)
123 			d = j;
124 		if(strchr(f[i], ':'))
125 			t = f[i];
126 	}
127 
128 	if(d==0 || wd==nil || m==nil || t==nil)
129 		return nil;
130 
131 	hh = strtol(t, 0, 10);
132 	mm = strtol(strchr(t, ':')+1, 0, 10);
133 	sprint(tmp, "%s %d %s %d:%.2d", wd, d, m, hh, mm);
134 	return estrdup(tmp);
135 }
136 
137 void
msgheadline(Biobuf * bin,int n,Biobuf * bout)138 msgheadline(Biobuf *bin, int n, Biobuf *bout)
139 {
140 	char *p, *q;
141 	char *date;
142 	char *from;
143 	char *subject;
144 
145 	date = nil;
146 	from = nil;
147 	subject = nil;
148 	while(p = Brdline(bin, '\n')){
149 		p[Blinelen(bin)-1] = '\0';
150 		if((q = strchr(p, ':')) == nil)
151 			continue;
152 		*q++ = '\0';
153 		if(cistrcmp(p, "from")==0)
154 			from = fixfrom(skipwhite(q));
155 		else if(cistrcmp(p, "subject")==0)
156 			subject = estrdup(skipwhite(q));
157 		else if(cistrcmp(p, "date")==0)
158 			date = fixdate(skipwhite(q));
159 	}
160 
161 	Bprint(bout, "%d/\t%s", n, from ? from : "");
162 	if(date)
163 		Bprint(bout, "\t%s", date);
164 	if(subject)
165 		Bprint(bout, "\n\t%s", subject);
166 	Bprint(bout, "\n");
167 
168 	free(date);
169 	free(from);
170 	free(subject);
171 }
172 
173 /*
174  * Write the headers for at most nshow messages to b,
175  * starting with hi and working down to lo.
176  * Return number of first message not scanned.
177  */
178 int
adddir(Biobuf * body,int hi,int lo,int nshow)179 adddir(Biobuf *body, int hi, int lo, int nshow)
180 {
181 	char *p, *q, tmp[40];
182 	int i, n;
183 	Biobuf *b;
184 
185 	n = 0;
186 	for(i=hi; i>=lo && n<nshow; i--){
187 		sprint(tmp, "%d", i);
188 		p = estrstrdup(dir, tmp);
189 		if(access(p, OREAD) < 0){
190 			free(p);
191 			break;
192 		}
193 		q = estrstrdup(p, "/header");
194 		free(p);
195 		b = Bopen(q, OREAD);
196 		free(q);
197 		if(b == nil)
198 			continue;
199 		msgheadline(b, i, body);
200 		Bterm(b);
201 		n++;
202 	}
203 	return i;
204 }
205 
206 /*
207  * Show the first nshow messages in the window.
208  * This depends on nntpfs presenting contiguously
209  * numbered directories, and on the qid version being
210  * the topmost numbered directory.
211  */
212 void
dirwindow(Window * w)213 dirwindow(Window *w)
214 {
215 	if((hi=gethi()) < 0)
216 		return;
217 
218 	if(w->data < 0)
219 		w->data = winopenfile(w, "data");
220 
221 	fprint(w->ctl, "dirty\n");
222 
223 	winopenbody(w, OWRITE);
224 	lo = adddir(w->body, hi, 0, nshow);
225 	winclean(w);
226 }
227 
228 /* return 1 if handled, 0 otherwise */
229 static int
iscmd(char * s,char * cmd)230 iscmd(char *s, char *cmd)
231 {
232 	int len;
233 
234 	len = strlen(cmd);
235 	return strncmp(s, cmd, len)==0 && (s[len]=='\0' || s[len]==' ' || s[len]=='\t' || s[len]=='\n');
236 }
237 
238 static char*
skip(char * s,char * cmd)239 skip(char *s, char *cmd)
240 {
241 	s += strlen(cmd);
242 	while(*s==' ' || *s=='\t' || *s=='\n')
243 		s++;
244 	return s;
245 }
246 
247 void
unlink(Article * m)248 unlink(Article *m)
249 {
250 	if(mlist == m)
251 		mlist = m->next;
252 
253 	if(m->next)
254 		m->next->prev = m->prev;
255 	if(m->prev)
256 		m->prev->next = m->next;
257 	m->next = m->prev = nil;
258 }
259 
260 int mesgopen(char*);
261 int fillmesgwindow(int, Article*);
262 Article *newpost(void);
263 void replywindow(Article*);
264 void mesgpost(Article*);
265 
266 /*
267  * Depends on d.qid.vers being highest numbered message in dir.
268  */
269 void
acmetimer(Article * m,Window * w)270 acmetimer(Article *m, Window *w)
271 {
272 	Biobuf *b;
273 	Dir *d;
274 
275 	assert(m==nil && w==root);
276 
277 	if((d = dirstat(dir))==nil | hi==d->qid.vers){
278 		free(d);
279 		return;
280 	}
281 
282 	if(w->data < 0)
283 		w->data = winopenfile(w, "data");
284 	if(winsetaddr(w, "0", 0))
285 		write(w->data, "", 0);
286 
287 	b = emalloc(sizeof(*b));
288 	Binit(b, w->data, OWRITE);
289 	adddir(b, d->qid.vers, hi+1, d->qid.vers);
290 	hi = d->qid.vers;
291 	Bterm(b);
292 	free(b);
293 	free(d);
294 	winselect(w, "0,.", 0);
295 }
296 
297 int
acmeload(Article *,Window *,char * s)298 acmeload(Article*, Window*, char *s)
299 {
300 	int nopen;
301 
302 //fprint(2, "load %s\n", s);
303 
304 	nopen = 0;
305 	while(*s){
306 		/* skip directory name */
307 		if(strncmp(s, dir, strlen(dir))==0)
308 			s += strlen(dir);
309 		nopen += mesgopen(s);
310 		if((s = strchr(s, '\n')) == nil)
311 			break;
312 		s = skip(s, "");
313 	}
314 	return nopen;
315 }
316 
317 int
acmecmd(Article * m,Window * w,char * s)318 acmecmd(Article *m, Window *w, char *s)
319 {
320 	int n;
321 	Biobuf *b;
322 
323 //fprint(2, "cmd %s\n", s);
324 
325 	s = skip(s, "");
326 
327 	if(iscmd(s, "Del")){
328 		if(m == nil){	/* don't close dir until messages close */
329 			if(mlist != nil){
330 				ctlprint(mlist->w->ctl, "show\n");
331 				return 1;
332 			}
333 			if(windel(w, 0))
334 				threadexitsall(nil);
335 			return 1;
336 		}else{
337 			if(windel(w, 0))
338 				m->dead = 1;
339 			return 1;
340 		}
341 	}
342 	if(m==nil && iscmd(s, "More")){
343 		s = skip(s, "More");
344 		if(n = atoi(s))
345 			nshow = n;
346 
347 		if(w->data < 0)
348 			w->data = winopenfile(w, "data");
349 		winsetaddr(w, "$", 1);
350 
351 		fprint(w->ctl, "dirty\n");
352 
353 		b = emalloc(sizeof(*b));
354 		Binit(b, w->data, OWRITE);
355 		lo = adddir(b, lo, 0, nshow);
356 		Bterm(b);
357 		free(b);
358 		winclean(w);
359 		winsetaddr(w, ".,", 0);
360 	}
361 	if(m!=nil && !m->ispost && iscmd(s, "Headers")){
362 		m->headers = !m->headers;
363 		fillmesgwindow(-1, m);
364 		return 1;
365 	}
366 	if(iscmd(s, "Newpost")){
367 		m = newpost();
368 		winopenbody(m->w, OWRITE);
369 		Bprint(m->w->body, "%s\nsubject: \n\n", group);
370 		winclean(m->w);
371 		winselect(m->w, "$", 0);
372 		return 1;
373 	}
374 	if(m!=nil && !m->ispost && iscmd(s, "Reply")){
375 		replywindow(m);
376 		return 1;
377 	}
378 //	if(m!=nil && iscmd(s, "Replymail")){
379 //		fprint(2, "no replymail yet\n");
380 //		return 1;
381 //	}
382 	if(iscmd(s, "Post")){
383 		mesgpost(m);
384 		return 1;
385 	}
386 	return 0;
387 }
388 
389 void
acmeevent(Article * m,Window * w,Event * e)390 acmeevent(Article *m, Window *w, Event *e)
391 {
392 	Event *ea, *e2, *eq;
393 	char *s, *t, *buf;
394 	int na;
395 	//int n;
396 	//ulong q0, q1;
397 
398 	switch(e->c1){	/* origin of action */
399 	default:
400 	Unknown:
401 		fprint(2, "unknown message %c%c\n", e->c1, e->c2);
402 		break;
403 
404 	case 'T':	/* bogus timer event! */
405 		acmetimer(m, w);
406 		break;
407 
408 	case 'F':	/* generated by our actions; ignore */
409 		break;
410 
411 	case 'E':	/* write to body or tag; can't affect us */
412 		break;
413 
414 	case 'K':	/* type away; we don't care */
415 		if(m && (e->c2 == 'I' || e->c2 == 'D')){
416 			m->dirtied = 1;
417 			if(!m->sayspost){
418 				wintagwrite(w, "Post ", 5);
419 				m->sayspost = 1;
420 			}
421 		}
422 		break;
423 
424 	case 'M':	/* mouse event */
425 		switch(e->c2){		/* type of action */
426 		case 'x':	/* mouse: button 2 in tag */
427 		case 'X':	/* mouse: button 2 in body */
428 			ea = nil;
429 			//e2 = nil;
430 			s = e->b;
431 			if(e->flag & 2){	/* null string with non-null expansion */
432 				e2 = recvp(w->cevent);
433 				if(e->nb==0)
434 					s = e2->b;
435 			}
436 			if(e->flag & 8){	/* chorded argument */
437 				ea = recvp(w->cevent);	/* argument */
438 				na = ea->nb;
439 				recvp(w->cevent);		/* ignore origin */
440 			}else
441 				na = 0;
442 
443 			/* append chorded arguments */
444 			if(na){
445 				t = emalloc(strlen(s)+1+na+1);
446 				sprint(t, "%s %s", s, ea->b);
447 				s = t;
448 			}
449 			/* if it's a known command, do it */
450 			/* if it's a long message, it can't be for us anyway */
451 		//	DPRINT(2, "exec: %s\n", s);
452 			if(!acmecmd(m, w, s))	/* send it back */
453 				winwriteevent(w, e);
454 			if(na)
455 				free(s);
456 			break;
457 
458 		case 'l':	/* mouse: button 3 in tag */
459 		case 'L':	/* mouse: button 3 in body */
460 			//buf = nil;
461 			eq = e;
462 			if(e->flag & 2){
463 				e2 = recvp(w->cevent);
464 				eq = e2;
465 			}
466 			s = eq->b;
467 			if(eq->q1>eq->q0 && eq->nb==0){
468 				buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
469 				winread(w, eq->q0, eq->q1, buf);
470 				s = buf;
471 			}
472 			if(!acmeload(m, w, s))
473 				winwriteevent(w, e);
474 			break;
475 
476 		case 'i':	/* mouse: text inserted in tag */
477 		case 'd':	/* mouse: text deleted from tag */
478 			break;
479 
480 		case 'I':	/* mouse: text inserted in body */
481 		case 'D':	/* mouse: text deleted from body */
482 			if(m == nil)
483 				break;
484 
485 			m->dirtied = 1;
486 			if(!m->sayspost){
487 				wintagwrite(w, "Post ", 5);
488 				m->sayspost = 1;
489 			}
490 			break;
491 
492 		default:
493 			goto Unknown;
494 		}
495 	}
496 }
497 
498 void
dirthread(void * v)499 dirthread(void *v)
500 {
501 	Event *e;
502 	Window *w;
503 
504 	w = v;
505 	while(e = recvp(w->cevent))
506 		acmeevent(nil, w, e);
507 
508 	threadexitsall(nil);
509 }
510 
511 void
mesgthread(void * v)512 mesgthread(void *v)
513 {
514 	Event *e;
515 	Article *m;
516 
517 	m = v;
518 	while(!m->dead && (e = recvp(m->w->cevent)))
519 		acmeevent(m, m->w, e);
520 
521 //fprint(2, "msg %p exits\n", m);
522 	unlink(m);
523 	free(m->w);
524 	free(m);
525 	threadexits(nil);
526 }
527 
528 /*
529 Xref: news.research.att.com comp.os.plan9:7360
530 Newsgroups: comp.os.plan9
531 Path: news.research.att.com!batch0!uunet!ffx.uu.net!finch!news.mindspring.net!newsfeed.mathworks.com!fu-berlin.de!server1.netnews.ja.net!hgmp.mrc.ac.uk!pegasus.csx.cam.ac.uk!bath.ac.uk!ccsdhd
532 From: Stephen Adam <saadam@bigpond.com>
533 Subject: Future of Plan9
534 Approved: plan9mod@bath.ac.uk
535 X-Newsreader: Microsoft Outlook Express 5.00.2014.211
536 X-Mimeole: Produced By Microsoft MimeOLE V5.00.2014.211
537 Sender: ccsdhd@bath.ac.uk (Dennis Davis)
538 Nntp-Posting-Date: Wed, 13 Dec 2000 21:28:45 EST
539 NNTP-Posting-Host: 203.54.121.233
540 Organization: Telstra BigPond Internet Services (http://www.bigpond.com)
541 X-Date: Wed, 13 Dec 2000 20:43:37 +1000
542 Lines: 12
543 Message-ID: <xbIZ5.157945$e5.114349@newsfeeds.bigpond.com>
544 References: <95pghu$3lf$1@news.fas.harvard.edu> <95ph36$3m9$1@news.fas.harvard.edu> <slrn980iic.u5q.mperrin@hcs.harvard.edu> <95png6$4ln$1@news.fas.harvard.edu> <95poqg$4rq$1@news.fas.harvard.edu> <slrn980vh8.2gb.myLastName@is07.fas.harvard.edu> <95q40h$66c$2@news.fas.harvard.edu> <95qjhu$8ke$1@news.fas.harvard.edu> <95riue$bu2$1@news.fas.harvard.edu> <95rnar$cbu$1@news.fas.harvard.edu>
545 X-Msmail-Priority: Normal
546 X-Trace: newsfeeds.bigpond.com 976703325 203.54.121.233 (Wed, 13 Dec 2000 21:28:45 EST)
547 X-Priority: 3
548 Date: Wed, 13 Dec 2000 10:49:50 GMT
549 */
550 
551 char *skipheader[] =
552 {
553 	"x-",
554 	"path:",
555 	"xref:",
556 	"approved:",
557 	"sender:",
558 	"nntp-",
559 	"organization:",
560 	"lines:",
561 	"message-id:",
562 	"references:",
563 	"reply-to:",
564 	"mime-",
565 	"content-",
566 };
567 
568 int
fillmesgwindow(int fd,Article * m)569 fillmesgwindow(int fd, Article *m)
570 {
571 	Biobuf *b;
572 	char *p, tmp[40];
573 	int i, inhdr, copy, xfd;
574 	Window *w;
575 
576 	xfd = -1;
577 	if(fd == -1){
578 		sprint(tmp, "%d/article", m->n);
579 		p = estrstrdup(dir, tmp);
580 		if((xfd = open(p, OREAD)) < 0){
581 			free(p);
582 			return 0;
583 		}
584 		free(p);
585 		fd = xfd;
586 	}
587 
588 	w = m->w;
589 	if(w->data < 0)
590 		w->data = winopenfile(w, "data");
591 	if(winsetaddr(w, ",", 0))
592 		write(w->data, "", 0);
593 
594 	winopenbody(m->w, OWRITE);
595 	b = emalloc(sizeof(*b));
596 	Binit(b, fd, OREAD);
597 
598 	inhdr = 1;
599 	copy = 1;
600 	while(p = Brdline(b, '\n')){
601 		if(Blinelen(b)==1)
602 			inhdr = 0, copy=1;
603 		if(inhdr && !isspace(p[0])){
604 			copy = 1;
605 			if(!m->headers){
606 				if(cistrncmp(p, "from:", 5)==0){
607 					p[Blinelen(b)-1] = '\0';
608 					p = fixfrom(skip(p, "from:"));
609 					Bprint(m->w->body, "From: %s\n", p);
610 					free(p);
611 					copy = 0;
612 					continue;
613 				}
614 				for(i=0; i<nelem(skipheader); i++)
615 					if(cistrncmp(p, skipheader[i], strlen(skipheader[i]))==0)
616 						copy=0;
617 			}
618 		}
619 		if(copy)
620 			Bwrite(m->w->body, p, Blinelen(b));
621 	}
622 	Bterm(b);
623 	free(b);
624 	winclean(m->w);
625 	if(xfd != -1)
626 		close(xfd);
627 	return 1;
628 }
629 
630 Article*
newpost(void)631 newpost(void)
632 {
633 	Article *m;
634 	char *p, tmp[40];
635 	static int nnew;
636 
637 	m = emalloc(sizeof *m);
638 	sprint(tmp, "Post%d", ++nnew);
639 	p = estrstrdup(dir, tmp);
640 
641 	m->w = newwindow();
642 	proccreate(wineventproc, m->w, STACK);
643 	winname(m->w, p);
644 	wintagwrite(m->w, "Post ", 5);
645 	m->sayspost = 1;
646 	m->ispost = 1;
647 	threadcreate(mesgthread, m, STACK);
648 
649 	if(mlist){
650 		m->next = mlist;
651 		mlist->prev = m;
652 	}
653 	mlist = m;
654 	return m;
655 }
656 
657 void
replywindow(Article * m)658 replywindow(Article *m)
659 {
660 	Biobuf *b;
661 	char *p, *ep, *q, tmp[40];
662 	int fd, copy;
663 	Article *reply;
664 
665 	sprint(tmp, "%d/article", m->n);
666 	p = estrstrdup(dir, tmp);
667 	if((fd = open(p, OREAD)) < 0){
668 		free(p);
669 		return;
670 	}
671 	free(p);
672 
673 	reply = newpost();
674 	winopenbody(reply->w, OWRITE);
675 	b = emalloc(sizeof(*b));
676 	Binit(b, fd, OREAD);
677 	copy = 0;
678 	while(p = Brdline(b, '\n')){
679 		if(Blinelen(b)==1)
680 			break;
681 		ep = p+Blinelen(b);
682 		if(!isspace(*p)){
683 			copy = 0;
684 			if(cistrncmp(p, "newsgroups:", 11)==0){
685 				for(q=p+11; *q!='\n'; q++)
686 					if(*q==',')
687 						*q = ' ';
688 				copy = 1;
689 			}else if(cistrncmp(p, "subject:", 8)==0){
690 				if(!strstr(p, " Re:") && !strstr(p, " RE:") && !strstr(p, " re:")){
691 					p = skip(p, "subject:");
692 					ep[-1] = '\0';
693 					Bprint(reply->w->body, "Subject: Re: %s\n", p);
694 				}else
695 					copy = 1;
696 			}else if(cistrncmp(p, "message-id:", 11)==0){
697 				Bprint(reply->w->body, "References: ");
698 				p += 11;
699 				copy = 1;
700 			}
701 		}
702 		if(copy)
703 			Bwrite(reply->w->body, p, ep-p);
704 	}
705 	Bterm(b);
706 	close(fd);
707 	free(b);
708 	Bprint(reply->w->body, "\n");
709 	winclean(reply->w);
710 	winselect(reply->w, "$", 0);
711 }
712 
713 char*
skipbl(char * s,char * e)714 skipbl(char *s, char *e)
715 {
716 	while(s < e){
717 		if(*s!=' ' && *s!='\t' && *s!=',')
718 			break;
719 		s++;
720 	}
721 	return s;
722 }
723 
724 char*
findbl(char * s,char * e)725 findbl(char *s, char *e)
726 {
727 	while(s < e){
728 		if(*s==' ' || *s=='\t' || *s==',')
729 			break;
730 		s++;
731 	}
732 	return s;
733 }
734 
735 /*
736  * comma-separate possibly blank-separated strings in line; e points before newline
737  */
738 void
commas(char * s,char * e)739 commas(char *s, char *e)
740 {
741 	char *t;
742 
743 	/* may have initial blanks */
744 	s = skipbl(s, e);
745 	while(s < e){
746 		s = findbl(s, e);
747 		if(s == e)
748 			break;
749 		t = skipbl(s, e);
750 		if(t == e)	/* no more words */
751 			break;
752 		/* patch comma */
753 		*s++ = ',';
754 		while(s < t)
755 			*s++ = ' ';
756 	}
757 }
758 void
mesgpost(Article * m)759 mesgpost(Article *m)
760 {
761 	Biobuf *b;
762 	char *p, *ep;
763 	int isfirst, ishdr, havegroup, havefrom;
764 
765 	p = estrstrdup(dir, "post");
766 	if((b = Bopen(p, OWRITE)) == nil){
767 		fprint(2, "cannot open %s: %r\n", p);
768 		free(p);
769 		return;
770 	}
771 	free(p);
772 
773 	winopenbody(m->w, OREAD);
774 	ishdr = 1;
775 	isfirst = 1;
776 	havegroup = havefrom = 0;
777 	while(p = Brdline(m->w->body, '\n')){
778 		ep = p+Blinelen(m->w->body);
779 		if(ishdr && p+1==ep){
780 			if(!havegroup)
781 				Bprint(b, "Newsgroups: %s\n", group);
782 			if(!havefrom)
783 				Bprint(b, "From: %s\n", from);
784 			ishdr = 0;
785 		}
786 		if(ishdr){
787 			ep[-1] = '\0';
788 			if(isfirst && strchr(p, ':')==0){	/* group list */
789 				commas(p, ep);
790 				Bprint(b, "newsgroups: %s\n", p);
791 				havegroup = 1;
792 				isfirst = 0;
793 				continue;
794 			}
795 			if(cistrncmp(p, "newsgroup:", 10)==0){
796 				commas(skip(p, "newsgroup:"), ep);
797 				Bprint(b, "newsgroups: %s\n", skip(p, "newsgroup:"));
798 				havegroup = 1;
799 				continue;
800 			}
801 			if(cistrncmp(p, "newsgroups:", 11)==0){
802 				commas(skip(p, "newsgroups:"), ep);
803 				Bprint(b, "newsgroups: %s\n", skip(p, "newsgroups:"));
804 				havegroup = 1;
805 				continue;
806 			}
807 			if(cistrncmp(p, "from:", 5)==0)
808 				havefrom = 1;
809 			ep[-1] = '\n';
810 		}
811 		Bwrite(b, p, ep-p);
812 	}
813 	winclosebody(m->w);
814 	Bflush(b);
815 	if(write(Bfildes(b), "", 0) == 0)
816 		winclean(m->w);
817 	else
818 		fprint(2, "post: %r\n");
819 	Bterm(b);
820 }
821 
822 int
mesgopen(char * s)823 mesgopen(char *s)
824 {
825 	char *p, tmp[40];
826 	int fd, n;
827 	Article *m;
828 
829 	n = atoi(s);
830 	if(n==0)
831 		return 0;
832 
833 	for(m=mlist; m; m=m->next){
834 		if(m->n == n){
835 			ctlprint(m->w->ctl, "show\n");
836 			return 1;
837 		}
838 	}
839 
840 	sprint(tmp, "%d/article", n);
841 	p = estrstrdup(dir, tmp);
842 	if((fd = open(p, OREAD)) < 0){
843 		free(p);
844 		return 0;
845 	}
846 
847 	m = emalloc(sizeof(*m));
848 	m->w = newwindow();
849 	m->n = n;
850 	proccreate(wineventproc, m->w, STACK);
851 	p[strlen(p)-strlen("article")] = '\0';
852 	winname(m->w, p);
853 	if(canpost)
854 		wintagwrite(m->w, "Reply ", 6);
855 	wintagwrite(m->w, "Headers ", 8);
856 
857 	free(p);
858 	if(mlist){
859 		m->next = mlist;
860 		mlist->prev = m;
861 	}
862 	mlist = m;
863 	threadcreate(mesgthread, m, STACK);
864 
865 	fillmesgwindow(fd, m);
866 	close(fd);
867 	windormant(m->w);
868 	return 1;
869 }
870 
871 void
usage(void)872 usage(void)
873 {
874 	fprint(2, "usage: News [-d /mnt/news] comp.os.plan9\n");
875 	exits("usage");
876 }
877 
878 void
timerproc(void * v)879 timerproc(void *v)
880 {
881 	Event e;
882 	Window *w;
883 
884 	memset(&e, 0, sizeof e);
885 	e.c1 = 'T';
886 	w = v;
887 
888 	for(;;){
889 		sleep(60*1000);
890 		sendp(w->cevent, &e);
891 	}
892 }
893 
894 char*
findfrom(void)895 findfrom(void)
896 {
897 	char *p, *u;
898 	Biobuf *b;
899 
900 	u = getuser();
901 	if(u==nil)
902 		return "glenda";
903 
904 	p = estrstrstrdup("/usr/", u, "/lib/newsfrom");
905 	b = Bopen(p, OREAD);
906 	free(p);
907 	if(b){
908 		p = Brdline(b, '\n');
909 		if(p){
910 			p[Blinelen(b)-1] = '\0';
911 			p = estrdup(p);
912 			Bterm(b);
913 			return p;
914 		}
915 		Bterm(b);
916 	}
917 
918 	p = estrstrstrdup("/mail/box/", u, "/headers");
919 	b = Bopen(p, OREAD);
920 	free(p);
921 	if(b){
922 		while(p = Brdline(b, '\n')){
923 			p[Blinelen(b)-1] = '\0';
924 			if(cistrncmp(p, "from:", 5)==0){
925 				p = estrdup(skip(p, "from:"));
926 				Bterm(b);
927 				return p;
928 			}
929 		}
930 		Bterm(b);
931 	}
932 
933 	return u;
934 }
935 
936 void
threadmain(int argc,char ** argv)937 threadmain(int argc, char **argv)
938 {
939 	char *p, *q;
940 	Dir *d;
941 	Window *w;
942 
943 	ARGBEGIN{
944 	case 'D':
945 		debug++;
946 		break;
947 	case 'd':
948 		dir = EARGF(usage());
949 		break;
950 	default:
951 		usage();
952 		break;
953 	}ARGEND
954 
955 	if(argc != 1)
956 		usage();
957 
958 	from = findfrom();
959 
960 	group = estrdup(argv[0]);	/* someone will be cute */
961 	while(q=strchr(group, '/'))
962 		*q = '.';
963 
964 	p = estrdup(argv[0]);
965 	while(q=strchr(p, '.'))
966 		*q = '/';
967 	p = estrstrstrdup(dir, "/", p);
968 	cleanname(p);
969 
970 	if((d = dirstat(p)) == nil){	/* maybe it is a new group */
971 		if((d = dirstat(dir)) == nil){
972 			fprint(2, "dirstat(%s) fails: %r\n", dir);
973 			threadexitsall(nil);
974 		}
975 		if((d->mode&DMDIR)==0){
976 			fprint(2, "%s not a directory\n", dir);
977 			threadexitsall(nil);
978 		}
979 		free(d);
980 		if((d = dirstat(p)) == nil){
981 			fprint(2, "stat %s: %r\n", p);
982 			threadexitsall(nil);
983 		}
984 	}
985 	if((d->mode&DMDIR)==0){
986 		fprint(2, "%s not a directory\n", dir);
987 		threadexitsall(nil);
988 	}
989 	free(d);
990 	dir = estrstrdup(p, "/");
991 
992 	q = estrstrdup(dir, "post");
993 	canpost = access(q, AWRITE)==0;
994 
995 	w = newwindow();
996 	root = w;
997 	proccreate(wineventproc, w, STACK);
998 	proccreate(timerproc, w, STACK);
999 
1000 	winname(w, dir);
1001 	if(canpost)
1002 		wintagwrite(w, "Newpost ", 8);
1003 	wintagwrite(w, "More ", 5);
1004 	dirwindow(w);
1005 	threadcreate(dirthread, w, STACK);
1006 	threadexits(nil);
1007 }
1008