xref: /plan9/sys/src/cmd/nntpfs.c (revision abfa367da87d91ea019c9536c4052275cb55ac26)
1 /*
2  * Network news transport protocol (NNTP) file server.
3  *
4  * Unfortunately, the file system differs from that expected
5  * by Charles Forsyth's rin news reader.  This is partially out
6  * of my own laziness, but it makes the bookkeeping here
7  * a lot easier.
8  */
9 
10 #include <u.h>
11 #include <libc.h>
12 #include <bio.h>
13 #include <auth.h>
14 #include <fcall.h>
15 #include <thread.h>
16 #include <9p.h>
17 
18 typedef struct Netbuf Netbuf;
19 typedef struct Group Group;
20 
21 struct Netbuf {
22 	Biobuf br;
23 	Biobuf bw;
24 	int lineno;
25 	int fd;
26 	int code;			/* last response code */
27 	int auth;			/* Authorization required? */
28 	char response[128];	/* last response */
29 	Group *currentgroup;
30 	char *addr;
31 	char *user;
32 	char *pass;
33 	ulong extended;	/* supported extensions */
34 };
35 
36 struct Group {
37 	char *name;
38 	Group *parent;
39 	Group **kid;
40 	int num;
41 	int nkid;
42 	int lo, hi;
43 	int canpost;
44 	int isgroup;	/* might just be piece of hierarchy */
45 	ulong mtime;
46 	ulong atime;
47 };
48 
49 /*
50  * First eight fields are, in order:
51  *	article number, subject, author, date, message-ID,
52  *	references, byte count, line count
53  * We don't support OVERVIEW.FMT; when I see a server with more
54  * interesting fields, I'll implement support then.  In the meantime,
55  * the standard defines the first eight fields.
56  */
57 
58 /* Extensions */
59 enum {
60 	Nxover   = (1<<0),
61 	Nxhdr    = (1<<1),
62 	Nxpat    = (1<<2),
63 	Nxlistgp = (1<<3),
64 };
65 
66 Group *root;
67 Netbuf *net;
68 ulong now;
69 int netdebug;
70 int readonly;
71 
72 void*
erealloc(void * v,ulong n)73 erealloc(void *v, ulong n)
74 {
75 	v = realloc(v, n);
76 	if(v == nil)
77 		sysfatal("out of memory reallocating %lud", n);
78 	setmalloctag(v, getcallerpc(&v));
79 	return v;
80 }
81 
82 void*
emalloc(ulong n)83 emalloc(ulong n)
84 {
85 	void *v;
86 
87 	v = malloc(n);
88 	if(v == nil)
89 		sysfatal("out of memory allocating %lud", n);
90 	memset(v, 0, n);
91 	setmalloctag(v, getcallerpc(&n));
92 	return v;
93 }
94 
95 char*
estrdup(char * s)96 estrdup(char *s)
97 {
98 	int l;
99 	char *t;
100 
101 	if (s == nil)
102 		return nil;
103 	l = strlen(s)+1;
104 	t = emalloc(l);
105 	memcpy(t, s, l);
106 	setmalloctag(t, getcallerpc(&s));
107 	return t;
108 }
109 
110 char*
estrdupn(char * s,int n)111 estrdupn(char *s, int n)
112 {
113 	int l;
114 	char *t;
115 
116 	l = strlen(s);
117 	if(l > n)
118 		l = n;
119 	t = emalloc(l+1);
120 	memmove(t, s, l);
121 	t[l] = '\0';
122 	setmalloctag(t, getcallerpc(&s));
123 	return t;
124 }
125 
126 char*
Nrdline(Netbuf * n)127 Nrdline(Netbuf *n)
128 {
129 	char *p;
130 	int l;
131 
132 	n->lineno++;
133 	Bflush(&n->bw);
134 	if((p = Brdline(&n->br, '\n')) == nil){
135 		werrstr("nntp eof");
136 		return nil;
137 	}
138 	p[l=Blinelen(&n->br)-1] = '\0';
139 	if(l > 0 && p[l-1] == '\r')
140 		p[l-1] = '\0';
141 if(netdebug)
142 	fprint(2, "-> %s\n", p);
143 	return p;
144 }
145 
146 int
nntpresponse(Netbuf * n,int e,char * cmd)147 nntpresponse(Netbuf *n, int e, char *cmd)
148 {
149 	int r;
150 	char *p;
151 
152 	for(;;){
153 		p = Nrdline(n);
154 		if(p==nil){
155 			strcpy(n->response, "early nntp eof");
156 			return -1;
157 		}
158 		r = atoi(p);
159 		if(r/100 == 1){	/* BUG? */
160 			fprint(2, "%s\n", p);
161 			continue;
162 		}
163 		break;
164 	}
165 
166 	strecpy(n->response, n->response+sizeof(n->response), p);
167 
168 	if((r=atoi(p)) == 0){
169 		close(n->fd);
170 		n->fd = -1;
171 		fprint(2, "bad nntp response: %s\n", p);
172 		werrstr("bad nntp response");
173 		return -1;
174 	}
175 
176 	n->code = r;
177 	if(0 < e && e<10 && r/100 != e){
178 		fprint(2, "%s: expected %dxx: got %s\n", cmd, e, n->response);
179 		return -1;
180 	}
181 	if(10 <= e && e<100 && r/10 != e){
182 		fprint(2, "%s: expected %dx: got %s\n", cmd, e, n->response);
183 		return -1;
184 	}
185 	if(100 <= e && r != e){
186 		fprint(2, "%s: expected %d: got %s\n", cmd, e, n->response);
187 		return -1;
188 	}
189 	return r;
190 }
191 
192 int nntpauth(Netbuf*);
193 int nntpxcmdprobe(Netbuf*);
194 int nntpcurrentgroup(Netbuf*, Group*);
195 
196 /* XXX: bug OVER/XOVER et al. */
197 static struct {
198 	ulong n;
199 	char *s;
200 } extensions [] = {
201 	{ Nxover, "OVER" },
202 	{ Nxhdr, "HDR" },
203 	{ Nxpat, "PAT" },
204 	{ Nxlistgp, "LISTGROUP" },
205 	{ 0, nil }
206 };
207 
208 static int indial;
209 
210 int
nntpconnect(Netbuf * n)211 nntpconnect(Netbuf *n)
212 {
213 	n->currentgroup = nil;
214 	close(n->fd);
215 	if((n->fd = dial(n->addr, nil, nil, nil)) < 0){
216 		snprint(n->response, sizeof n->response, "dial %s: %r", n->addr);
217 		return -1;
218 	}
219 	Binit(&n->br, n->fd, OREAD);
220 	Binit(&n->bw, n->fd, OWRITE);
221 	if(nntpresponse(n, 20, "greeting") < 0)
222 		return -1;
223 	readonly = (n->code == 201);
224 
225 	indial = 1;
226 	if(n->auth != 0)
227 		nntpauth(n);
228 //	nntpxcmdprobe(n);
229 	indial = 0;
230 	return 0;
231 }
232 
233 int
nntpcmd(Netbuf * n,char * cmd,int e)234 nntpcmd(Netbuf *n, char *cmd, int e)
235 {
236 	int tried;
237 
238 	tried = 0;
239 	for(;;){
240 		if(netdebug)
241 			fprint(2, "<- %s\n", cmd);
242 		Bprint(&n->bw, "%s\r\n", cmd);
243 		if(nntpresponse(n, e, cmd)>=0 && (e < 0 || n->code/100 != 5))
244 			return 0;
245 
246 		/* redial */
247 		if(indial || tried++ || nntpconnect(n) < 0)
248 			return -1;
249 	}
250 }
251 
252 int
nntpauth(Netbuf * n)253 nntpauth(Netbuf *n)
254 {
255 	char cmd[256];
256 
257 	snprint(cmd, sizeof cmd, "AUTHINFO USER %s", n->user);
258 	if (nntpcmd(n, cmd, -1) < 0 || n->code != 381) {
259 		fprint(2, "Authentication failed: %s\n", n->response);
260 		return -1;
261 	}
262 
263 	snprint(cmd, sizeof cmd, "AUTHINFO PASS %s", n->pass);
264 	if (nntpcmd(n, cmd, -1) < 0 || n->code != 281) {
265 		fprint(2, "Authentication failed: %s\n", n->response);
266 		return -1;
267 	}
268 
269 	return 0;
270 }
271 
272 int
nntpxcmdprobe(Netbuf * n)273 nntpxcmdprobe(Netbuf *n)
274 {
275 	int i;
276 	char *p;
277 
278 	n->extended = 0;
279 	if (nntpcmd(n, "LIST EXTENSIONS", 0) < 0 || n->code != 202)
280 		return 0;
281 
282 	while((p = Nrdline(n)) != nil) {
283 		if (strcmp(p, ".") == 0)
284 			break;
285 
286 		for(i=0; extensions[i].s != nil; i++)
287 			if (cistrcmp(extensions[i].s, p) == 0) {
288 				n->extended |= extensions[i].n;
289 				break;
290 			}
291 	}
292 	return 0;
293 }
294 
295 /* XXX: searching, lazy evaluation */
296 static int
overcmp(void * v1,void * v2)297 overcmp(void *v1, void *v2)
298 {
299 	int a, b;
300 
301 	a = atoi(*(char**)v1);
302 	b = atoi(*(char**)v2);
303 
304 	if(a < b)
305 		return -1;
306 	else if(a > b)
307 		return 1;
308 	return 0;
309 }
310 
311 enum {
312 	XoverChunk = 100,
313 };
314 
315 char *xover[XoverChunk];
316 int xoverlo;
317 int xoverhi;
318 int xovercount;
319 Group *xovergroup;
320 
321 char*
nntpover(Netbuf * n,Group * g,int m)322 nntpover(Netbuf *n, Group *g, int m)
323 {
324 	int i, lo, hi, mid, msg;
325 	char *p;
326 	char cmd[64];
327 
328 	if (g->isgroup == 0)	/* BUG: should check extension capabilities */
329 		return nil;
330 
331 	if(g != xovergroup || m < xoverlo || m >= xoverhi){
332 		lo = (m/XoverChunk)*XoverChunk;
333 		hi = lo+XoverChunk;
334 
335 		if(lo < g->lo)
336 			lo = g->lo;
337 		else if (lo > g->hi)
338 			lo = g->hi;
339 		if(hi < lo || hi > g->hi)
340 			hi = g->hi;
341 
342 		if(nntpcurrentgroup(n, g) < 0)
343 			return nil;
344 
345 		if(lo == hi)
346 			snprint(cmd, sizeof cmd, "XOVER %d", hi);
347 		else
348 			snprint(cmd, sizeof cmd, "XOVER %d-%d", lo, hi-1);
349 		if(nntpcmd(n, cmd, 224) < 0)
350 			return nil;
351 
352 		for(i=0; (p = Nrdline(n)) != nil; i++) {
353 			if(strcmp(p, ".") == 0)
354 				break;
355 			if(i >= XoverChunk)
356 				sysfatal("news server doesn't play by the rules");
357 			free(xover[i]);
358 			xover[i] = emalloc(strlen(p)+2);
359 			strcpy(xover[i], p);
360 			strcat(xover[i], "\n");
361 		}
362 		qsort(xover, i, sizeof(xover[0]), overcmp);
363 
364 		xovercount = i;
365 
366 		xovergroup = g;
367 		xoverlo = lo;
368 		xoverhi = hi;
369 	}
370 
371 	lo = 0;
372 	hi = xovercount;
373 	/* search for message */
374 	while(lo < hi){
375 		mid = (lo+hi)/2;
376 		msg = atoi(xover[mid]);
377 		if(m == msg)
378 			return xover[mid];
379 		else if(m < msg)
380 			hi = mid;
381 		else
382 			lo = mid+1;
383 	}
384 	return nil;
385 }
386 
387 /*
388  * Return the new Group structure for the group name.
389  * Destroys name.
390  */
391 static int printgroup(char*,Group*);
392 Group*
findgroup(Group * g,char * name,int mk)393 findgroup(Group *g, char *name, int mk)
394 {
395 	int lo, hi, m;
396 	char *p, *q;
397 	static int ngroup;
398 
399 	for(p=name; *p; p=q){
400 		if(q = strchr(p, '.'))
401 			*q++ = '\0';
402 		else
403 			q = p+strlen(p);
404 
405 		lo = 0;
406 		hi = g->nkid;
407 		while(hi-lo > 1){
408 			m = (lo+hi)/2;
409 			if(strcmp(p, g->kid[m]->name) < 0)
410 				hi = m;
411 			else
412 				lo = m;
413 		}
414 		assert(lo==hi || lo==hi-1);
415 		if(lo==hi || strcmp(p, g->kid[lo]->name) != 0){
416 			if(mk==0)
417 				return nil;
418 			if(g->nkid%16 == 0)
419 				g->kid = erealloc(g->kid, (g->nkid+16)*sizeof(g->kid[0]));
420 
421 			/*
422 			 * if we're down to a single place 'twixt lo and hi, the insertion might need
423 			 * to go at lo or at hi.  strcmp to find out.  the list needs to stay sorted.
424 		 	 */
425 			if(lo==hi-1 && strcmp(p, g->kid[lo]->name) < 0)
426 				hi = lo;
427 
428 			if(hi < g->nkid)
429 				memmove(g->kid+hi+1, g->kid+hi, sizeof(g->kid[0])*(g->nkid-hi));
430 			g->nkid++;
431 			g->kid[hi] = emalloc(sizeof(*g));
432 			g->kid[hi]->parent = g;
433 			g = g->kid[hi];
434 			g->name = estrdup(p);
435 			g->num = ++ngroup;
436 			g->mtime = time(0);
437 		}else
438 			g = g->kid[lo];
439 	}
440 	if(mk)
441 		g->isgroup = 1;
442 	return g;
443 }
444 
445 static int
printgroup(char * s,Group * g)446 printgroup(char *s, Group *g)
447 {
448 	if(g->parent == g)
449 		return 0;
450 
451 	if(printgroup(s, g->parent))
452 		strcat(s, ".");
453 	strcat(s, g->name);
454 	return 1;
455 }
456 
457 static char*
Nreaddata(Netbuf * n)458 Nreaddata(Netbuf *n)
459 {
460 	char *p, *q;
461 	int l;
462 
463 	p = nil;
464 	l = 0;
465 	for(;;){
466 		q = Nrdline(n);
467 		if(q==nil){
468 			free(p);
469 			return nil;
470 		}
471 		if(strcmp(q, ".")==0)
472 			return p;
473 		if(q[0]=='.')
474 			q++;
475 		p = erealloc(p, l+strlen(q)+1+1);
476 		strcpy(p+l, q);
477 		strcat(p+l, "\n");
478 		l += strlen(p+l);
479 	}
480 }
481 
482 /*
483  * Return the output of a HEAD, BODY, or ARTICLE command.
484  */
485 char*
nntpget(Netbuf * n,Group * g,int msg,char * retr)486 nntpget(Netbuf *n, Group *g, int msg, char *retr)
487 {
488 	char *s;
489 	char cmd[1024];
490 
491 	if(g->isgroup == 0){
492 		werrstr("not a group");
493 		return nil;
494 	}
495 
496 	if(strcmp(retr, "XOVER") == 0){
497 		s = nntpover(n, g, msg);
498 		if(s == nil)
499 			s = "";
500 		return estrdup(s);
501 	}
502 
503 	if(nntpcurrentgroup(n, g) < 0)
504 		return nil;
505 	sprint(cmd, "%s %d", retr, msg);
506 	nntpcmd(n, cmd, 0);
507 	if(n->code/10 != 22)
508 		return nil;
509 
510 	return Nreaddata(n);
511 }
512 
513 int
nntpcurrentgroup(Netbuf * n,Group * g)514 nntpcurrentgroup(Netbuf *n, Group *g)
515 {
516 	char cmd[1024];
517 
518 	if(n->currentgroup != g){
519 		strcpy(cmd, "GROUP ");
520 		printgroup(cmd, g);
521 		if(nntpcmd(n, cmd, 21) < 0)
522 			return -1;
523 		n->currentgroup = g;
524 	}
525 	return 0;
526 }
527 
528 void
nntprefreshall(Netbuf * n)529 nntprefreshall(Netbuf *n)
530 {
531 	char *f[10], *p;
532 	int hi, lo, nf;
533 	Group *g;
534 
535 	if(nntpcmd(n, "LIST", 21) < 0)
536 		return;
537 
538 	while(p = Nrdline(n)){
539 		if(strcmp(p, ".")==0)
540 			break;
541 
542 		nf = getfields(p, f, nelem(f), 1, "\t\r\n ");
543 		if(nf != 4){
544 			int i;
545 			for(i=0; i<nf; i++)
546 				fprint(2, "%s%s", i?" ":"", f[i]);
547 			fprint(2, "\n");
548 			fprint(2, "syntax error in group list, line %d", n->lineno);
549 			return;
550 		}
551 		g = findgroup(root, f[0], 1);
552 		hi = strtol(f[1], 0, 10)+1;
553 		lo = strtol(f[2], 0, 10);
554 		if(g->hi != hi){
555 			g->hi = hi;
556 			if(g->lo==0)
557 				g->lo = lo;
558 			g->canpost = f[3][0] == 'y';
559 			g->mtime = time(0);
560 		}
561 	}
562 }
563 
564 void
nntprefresh(Netbuf * n,Group * g)565 nntprefresh(Netbuf *n, Group *g)
566 {
567 	char cmd[1024];
568 	char *f[5];
569 	int lo, hi;
570 
571 	if(g->isgroup==0)
572 		return;
573 
574 	if(time(0) - g->atime < 30)
575 		return;
576 
577 	strcpy(cmd, "GROUP ");
578 	printgroup(cmd, g);
579 	if(nntpcmd(n, cmd, 21) < 0){
580 		n->currentgroup = nil;
581 		return;
582 	}
583 	n->currentgroup = g;
584 
585 	if(tokenize(n->response, f, nelem(f)) < 4){
586 		fprint(2, "error reading GROUP response");
587 		return;
588 	}
589 
590 	/* backwards from LIST! */
591 	hi = strtol(f[3], 0, 10)+1;
592 	lo = strtol(f[2], 0, 10);
593 	if(g->hi != hi){
594 		g->mtime = time(0);
595 		if(g->lo==0)
596 			g->lo = lo;
597 		g->hi = hi;
598 	}
599 	g->atime = time(0);
600 }
601 
602 char*
nntppost(Netbuf * n,char * msg)603 nntppost(Netbuf *n, char *msg)
604 {
605 	char *p, *q;
606 
607 	if(nntpcmd(n, "POST", 34) < 0)
608 		return n->response;
609 
610 	for(p=msg; *p; p=q){
611 		if(q = strchr(p, '\n'))
612 			*q++ = '\0';
613 		else
614 			q = p+strlen(p);
615 
616 		if(p[0]=='.')
617 			Bputc(&n->bw, '.');
618 		Bwrite(&n->bw, p, strlen(p));
619 		Bputc(&n->bw, '\r');
620 		Bputc(&n->bw, '\n');
621 	}
622 	Bprint(&n->bw, ".\r\n");
623 
624 	if(nntpresponse(n, 0, nil) < 0)
625 		return n->response;
626 
627 	if(n->code/100 != 2)
628 		return n->response;
629 	return nil;
630 }
631 
632 /*
633  * Because an expanded QID space makes thngs much easier,
634  * we sleazily use the version part of the QID as more path bits.
635  * Since we make sure not to mount ourselves cached, this
636  * doesn't break anything (unless you want to bind on top of
637  * things in this file system).  In the next version of 9P, we'll
638  * have more QID bits to play with.
639  *
640  * The newsgroup is encoded in the top 15 bits
641  * of the path.  The message number is the bottom 17 bits.
642  * The file within the message directory is in the version [sic].
643  */
644 
645 enum {	/* file qids */
646 	Qhead,
647 	Qbody,
648 	Qarticle,
649 	Qxover,
650 	Nfile,
651 };
652 char *filename[] = {
653 	"header",
654 	"body",
655 	"article",
656 	"xover",
657 };
658 char *nntpname[] = {
659 	"HEAD",
660 	"BODY",
661 	"ARTICLE",
662 	"XOVER",
663 };
664 
665 #define GROUP(p)	(((p)>>17)&0x3FFF)
666 #define MESSAGE(p)	((p)&0x1FFFF)
667 #define FILE(v)		((v)&0x3)
668 
669 #define PATH(g,m)	((((g)&0x3FFF)<<17)|((m)&0x1FFFF))
670 #define POST(g)	PATH(0,g,0)
671 #define VERS(f)		((f)&0x3)
672 
673 typedef struct Aux Aux;
674 struct Aux {
675 	Group *g;
676 	int n;
677 	int ispost;
678 	int file;
679 	char *s;
680 	int ns;
681 	int offset;
682 };
683 
684 static void
fsattach(Req * r)685 fsattach(Req *r)
686 {
687 	Aux *a;
688 	char *spec;
689 
690 	spec = r->ifcall.aname;
691 	if(spec && spec[0]){
692 		respond(r, "invalid attach specifier");
693 		return;
694 	}
695 
696 	a = emalloc(sizeof *a);
697 	a->g = root;
698 	a->n = -1;
699 	r->fid->aux = a;
700 
701 	r->ofcall.qid = (Qid){0, 0, QTDIR};
702 	r->fid->qid = r->ofcall.qid;
703 	respond(r, nil);
704 }
705 
706 static char*
fsclone(Fid * ofid,Fid * fid)707 fsclone(Fid *ofid, Fid *fid)
708 {
709 	Aux *a;
710 
711 	a = emalloc(sizeof(*a));
712 	*a = *(Aux*)ofid->aux;
713 	fid->aux = a;
714 	return nil;
715 }
716 
717 static char*
fswalk1(Fid * fid,char * name,Qid * qid)718 fswalk1(Fid *fid, char *name, Qid *qid)
719 {
720 	char *p;
721 	int i, isdotdot, n;
722 	Aux *a;
723 	Group *ng;
724 
725 	isdotdot = strcmp(name, "..")==0;
726 
727 	a = fid->aux;
728 	if(a->s)	/* file */
729 		return "protocol botch";
730 	if(a->n != -1){
731 		if(isdotdot){
732 			*qid = (Qid){PATH(a->g->num, 0), 0, QTDIR};
733 			fid->qid = *qid;
734 			a->n = -1;
735 			return nil;
736 		}
737 		for(i=0; i<Nfile; i++){
738 			if(strcmp(name, filename[i])==0){
739 				if(a->s = nntpget(net, a->g, a->n, nntpname[i])){
740 					*qid = (Qid){PATH(a->g->num, a->n), Qbody, 0};
741 					fid->qid = *qid;
742 					a->file = i;
743 					return nil;
744 				}else
745 					return "file does not exist";
746 			}
747 		}
748 		return "file does not exist";
749 	}
750 
751 	if(isdotdot){
752 		a->g = a->g->parent;
753 		*qid = (Qid){PATH(a->g->num, 0), 0, QTDIR};
754 		fid->qid = *qid;
755 		return nil;
756 	}
757 
758 	if(a->g->isgroup && !readonly && a->g->canpost
759 	&& strcmp(name, "post")==0){
760 		a->ispost = 1;
761 		*qid = (Qid){PATH(a->g->num, 0), 0, 0};
762 		fid->qid = *qid;
763 		return nil;
764 	}
765 
766 	if(ng = findgroup(a->g, name, 0)){
767 		a->g = ng;
768 		*qid = (Qid){PATH(a->g->num, 0), 0, QTDIR};
769 		fid->qid = *qid;
770 		return nil;
771 	}
772 
773 	n = strtoul(name, &p, 0);
774 	if('0'<=name[0] && name[0]<='9' && *p=='\0' && a->g->lo<=n && n<a->g->hi){
775 		a->n = n;
776 		*qid = (Qid){PATH(a->g->num, n+1-a->g->lo), 0, QTDIR};
777 		fid->qid = *qid;
778 		return nil;
779 	}
780 
781 	return "file does not exist";
782 }
783 
784 static void
fsopen(Req * r)785 fsopen(Req *r)
786 {
787 	Aux *a;
788 
789 	a = r->fid->aux;
790 	if((a->ispost && (r->ifcall.mode&~OTRUNC) != OWRITE)
791 	|| (!a->ispost && r->ifcall.mode != OREAD))
792 		respond(r, "permission denied");
793 	else
794 		respond(r, nil);
795 }
796 
797 static void
fillstat(Dir * d,Aux * a)798 fillstat(Dir *d, Aux *a)
799 {
800 	char buf[32];
801 	Group *g;
802 
803 	memset(d, 0, sizeof *d);
804 	d->uid = estrdup("nntp");
805 	d->gid = estrdup("nntp");
806 	g = a->g;
807 	d->atime = d->mtime = g->mtime;
808 
809 	if(a->ispost){
810 		d->name = estrdup("post");
811 		d->mode = 0222;
812 		d->qid = (Qid){PATH(g->num, 0), 0, 0};
813 		d->length = a->ns;
814 		return;
815 	}
816 
817 	if(a->s){	/* article file */
818 		d->name = estrdup(filename[a->file]);
819 		d->mode = 0444;
820 		d->qid = (Qid){PATH(g->num, a->n+1-g->lo), a->file, 0};
821 		return;
822 	}
823 
824 	if(a->n != -1){	/* article directory */
825 		sprint(buf, "%d", a->n);
826 		d->name = estrdup(buf);
827 		d->mode = DMDIR|0555;
828 		d->qid = (Qid){PATH(g->num, a->n+1-g->lo), 0, QTDIR};
829 		return;
830 	}
831 
832 	/* group directory */
833 	if(g->name[0])
834 		d->name = estrdup(g->name);
835 	else
836 		d->name = estrdup("/");
837 	d->mode = DMDIR|0555;
838 	d->qid = (Qid){PATH(g->num, 0), g->hi-1, QTDIR};
839 }
840 
841 static int
dirfillstat(Dir * d,Aux * a,int i)842 dirfillstat(Dir *d, Aux *a, int i)
843 {
844 	int ndir;
845 	Group *g;
846 	char buf[32];
847 
848 	memset(d, 0, sizeof *d);
849 	d->uid = estrdup("nntp");
850 	d->gid = estrdup("nntp");
851 
852 	g = a->g;
853 	d->atime = d->mtime = g->mtime;
854 
855 	if(a->n != -1){	/* article directory */
856 		if(i >= Nfile)
857 			return -1;
858 
859 		d->name = estrdup(filename[i]);
860 		d->mode = 0444;
861 		d->qid = (Qid){PATH(g->num, a->n), i, 0};
862 		return 0;
863 	}
864 
865 	/* hierarchy directory: child groups */
866 	if(i < g->nkid){
867 		d->name = estrdup(g->kid[i]->name);
868 		d->mode = DMDIR|0555;
869 		d->qid = (Qid){PATH(g->kid[i]->num, 0), g->kid[i]->hi-1, QTDIR};
870 		return 0;
871 	}
872 	i -= g->nkid;
873 
874 	/* group directory: post file */
875 	if(g->isgroup && !readonly && g->canpost){
876 		if(i < 1){
877 			d->name = estrdup("post");
878 			d->mode = 0222;
879 			d->qid = (Qid){PATH(g->num, 0), 0, 0};
880 			return 0;
881 		}
882 		i--;
883 	}
884 
885 	/* group directory: child articles */
886 	ndir = g->hi - g->lo;
887 	if(i < ndir){
888 		sprint(buf, "%d", g->lo+i);
889 		d->name = estrdup(buf);
890 		d->mode = DMDIR|0555;
891 		d->qid = (Qid){PATH(g->num, i+1), 0, QTDIR};
892 		return 0;
893 	}
894 
895 	return -1;
896 }
897 
898 static void
fsstat(Req * r)899 fsstat(Req *r)
900 {
901 	Aux *a;
902 
903 	a = r->fid->aux;
904 	if(r->fid->qid.path == 0 && (r->fid->qid.type & QTDIR))
905 		nntprefreshall(net);
906 	else if(a->g->isgroup)
907 		nntprefresh(net, a->g);
908 	fillstat(&r->d, a);
909 	respond(r, nil);
910 }
911 
912 static void
fsread(Req * r)913 fsread(Req *r)
914 {
915 	int offset, n;
916 	Aux *a;
917 	char *p, *ep;
918 	Dir d;
919 
920 	a = r->fid->aux;
921 	if(a->s){
922 		readstr(r, a->s);
923 		respond(r, nil);
924 		return;
925 	}
926 
927 	if(r->ifcall.offset == 0)
928 		offset = 0;
929 	else
930 		offset = a->offset;
931 
932 	p = r->ofcall.data;
933 	ep = r->ofcall.data+r->ifcall.count;
934 	for(; p+2 < ep; p += n){
935 		if(dirfillstat(&d, a, offset) < 0)
936 			break;
937 		n=convD2M(&d, (uchar*)p, ep-p);
938 		free(d.name);
939 		free(d.uid);
940 		free(d.gid);
941 		free(d.muid);
942 		if(n <= BIT16SZ)
943 			break;
944 		offset++;
945 	}
946 	a->offset = offset;
947 	r->ofcall.count = p - r->ofcall.data;
948 	respond(r, nil);
949 }
950 
951 static void
fswrite(Req * r)952 fswrite(Req *r)
953 {
954 	Aux *a;
955 	long count;
956 	vlong offset;
957 
958 	a = r->fid->aux;
959 
960 	if(r->ifcall.count == 0){	/* commit */
961 		respond(r, nntppost(net, a->s));
962 		free(a->s);
963 		a->ns = 0;
964 		a->s = nil;
965 		return;
966 	}
967 
968 	count = r->ifcall.count;
969 	offset = r->ifcall.offset;
970 	if(a->ns < count+offset+1){
971 		a->s = erealloc(a->s, count+offset+1);
972 		a->ns = count+offset;
973 		a->s[a->ns] = '\0';
974 	}
975 	memmove(a->s+offset, r->ifcall.data, count);
976 	r->ofcall.count = count;
977 	respond(r, nil);
978 }
979 
980 static void
fsdestroyfid(Fid * fid)981 fsdestroyfid(Fid *fid)
982 {
983 	Aux *a;
984 
985 	a = fid->aux;
986 	if(a==nil)
987 		return;
988 
989 	if(a->ispost && a->s)
990 		nntppost(net, a->s);
991 
992 	free(a->s);
993 	free(a);
994 }
995 
996 Srv nntpsrv = {
997 .destroyfid=	fsdestroyfid,
998 .attach=	fsattach,
999 .clone=	fsclone,
1000 .walk1=	fswalk1,
1001 .open=	fsopen,
1002 .read=	fsread,
1003 .write=	fswrite,
1004 .stat=	fsstat,
1005 };
1006 
1007 void
usage(void)1008 usage(void)
1009 {
1010 	fprint(2, "usage: nntpsrv [-a] [-s service] [-m mtpt] [nntp.server]\n");
1011 	exits("usage");
1012 }
1013 
1014 void
dumpgroups(Group * g,int ind)1015 dumpgroups(Group *g, int ind)
1016 {
1017 	int i;
1018 
1019 	print("%*s%s\n", ind*4, "", g->name);
1020 	for(i=0; i<g->nkid; i++)
1021 		dumpgroups(g->kid[i], ind+1);
1022 }
1023 
1024 void
main(int argc,char ** argv)1025 main(int argc, char **argv)
1026 {
1027 	int auth, x;
1028 	char *mtpt, *service, *where, *user;
1029 	Netbuf n;
1030 	UserPasswd *up;
1031 
1032 	mtpt = "/mnt/news";
1033 	service = nil;
1034 	memset(&n, 0, sizeof n);
1035 	user = nil;
1036 	auth = 0;
1037 	ARGBEGIN{
1038 	case 'D':
1039 		chatty9p++;
1040 		break;
1041 	case 'N':
1042 		netdebug = 1;
1043 		break;
1044 	case 'a':
1045 		auth = 1;
1046 		break;
1047 	case 'u':
1048 		user = EARGF(usage());
1049 		break;
1050 	case 's':
1051 		service = EARGF(usage());
1052 		break;
1053 	case 'm':
1054 		mtpt = EARGF(usage());
1055 		break;
1056 	default:
1057 		usage();
1058 	}ARGEND
1059 
1060 	if(argc > 1)
1061 		usage();
1062 	if(argc==0)
1063 		where = "$nntp";
1064 	else
1065 		where = argv[0];
1066 
1067 	now = time(0);
1068 
1069 	net = &n;
1070 	if(auth) {
1071 		n.auth = 1;
1072 		if(user)
1073 			up = auth_getuserpasswd(auth_getkey, "proto=pass service=nntp server=%q user=%q", where, user);
1074 		else
1075 			up = auth_getuserpasswd(auth_getkey, "proto=pass service=nntp server=%q", where);
1076 		if(up == nil)
1077 			sysfatal("no password: %r");
1078 
1079 		n.user = up->user;
1080 		n.pass = up->passwd;
1081 	}
1082 
1083 	n.addr = netmkaddr(where, "tcp", "nntp");
1084 
1085 	root = emalloc(sizeof *root);
1086 	root->name = estrdup("");
1087 	root->parent = root;
1088 
1089 	n.fd = -1;
1090 	if(nntpconnect(&n) < 0)
1091 		sysfatal("nntpconnect: %s", n.response);
1092 
1093 	x=netdebug;
1094 	netdebug=0;
1095 	nntprefreshall(&n);
1096 	netdebug=x;
1097 //	dumpgroups(root, 0);
1098 
1099 	postmountsrv(&nntpsrv, service, mtpt, MREPL);
1100 	exits(nil);
1101 }
1102 
1103