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