]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/nntpfs.c
abaco: cleanup, handle image/x-icon, don't use backspace as a hotkey, and remove...
[plan9front.git] / sys / src / cmd / nntpfs.c
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*
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*
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*
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*
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*
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
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
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: %r");
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
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
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
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
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*
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*
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
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*
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*
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
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
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
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*
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
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*
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*
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
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
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
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
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
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
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
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
1008 usage(void)
1009 {
1010         fprint(2, "usage: nntpsrv [-a] [-s service] [-m mtpt] [nntp.server]\n");
1011         exits("usage");
1012 }
1013
1014 void
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
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