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