summaryrefslogtreecommitdiff
path: root/sys/src/cmd/nntpfs.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/nntpfs.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/nntpfs.c')
-rwxr-xr-xsys/src/cmd/nntpfs.c1102
1 files changed, 1102 insertions, 0 deletions
diff --git a/sys/src/cmd/nntpfs.c b/sys/src/cmd/nntpfs.c
new file mode 100755
index 000000000..442524d7f
--- /dev/null
+++ b/sys/src/cmd/nntpfs.c
@@ -0,0 +1,1102 @@
+/*
+ * Network news transport protocol (NNTP) file server.
+ *
+ * Unfortunately, the file system differs from that expected
+ * by Charles Forsyth's rin news reader. This is partially out
+ * of my own laziness, but it makes the bookkeeping here
+ * a lot easier.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+typedef struct Netbuf Netbuf;
+typedef struct Group Group;
+
+struct Netbuf {
+ Biobuf br;
+ Biobuf bw;
+ int lineno;
+ int fd;
+ int code; /* last response code */
+ int auth; /* Authorization required? */
+ char response[128]; /* last response */
+ Group *currentgroup;
+ char *addr;
+ char *user;
+ char *pass;
+ ulong extended; /* supported extensions */
+};
+
+struct Group {
+ char *name;
+ Group *parent;
+ Group **kid;
+ int num;
+ int nkid;
+ int lo, hi;
+ int canpost;
+ int isgroup; /* might just be piece of hierarchy */
+ ulong mtime;
+ ulong atime;
+};
+
+/*
+ * First eight fields are, in order:
+ * article number, subject, author, date, message-ID,
+ * references, byte count, line count
+ * We don't support OVERVIEW.FMT; when I see a server with more
+ * interesting fields, I'll implement support then. In the meantime,
+ * the standard defines the first eight fields.
+ */
+
+/* Extensions */
+enum {
+ Nxover = (1<<0),
+ Nxhdr = (1<<1),
+ Nxpat = (1<<2),
+ Nxlistgp = (1<<3),
+};
+
+Group *root;
+Netbuf *net;
+ulong now;
+int netdebug;
+int readonly;
+
+void*
+erealloc(void *v, ulong n)
+{
+ v = realloc(v, n);
+ if(v == nil)
+ sysfatal("out of memory reallocating %lud", n);
+ setmalloctag(v, getcallerpc(&v));
+ return v;
+}
+
+void*
+emalloc(ulong n)
+{
+ void *v;
+
+ v = malloc(n);
+ if(v == nil)
+ sysfatal("out of memory allocating %lud", n);
+ memset(v, 0, n);
+ setmalloctag(v, getcallerpc(&n));
+ return v;
+}
+
+char*
+estrdup(char *s)
+{
+ int l;
+ char *t;
+
+ if (s == nil)
+ return nil;
+ l = strlen(s)+1;
+ t = emalloc(l);
+ memcpy(t, s, l);
+ setmalloctag(t, getcallerpc(&s));
+ return t;
+}
+
+char*
+estrdupn(char *s, int n)
+{
+ int l;
+ char *t;
+
+ l = strlen(s);
+ if(l > n)
+ l = n;
+ t = emalloc(l+1);
+ memmove(t, s, l);
+ t[l] = '\0';
+ setmalloctag(t, getcallerpc(&s));
+ return t;
+}
+
+char*
+Nrdline(Netbuf *n)
+{
+ char *p;
+ int l;
+
+ n->lineno++;
+ Bflush(&n->bw);
+ if((p = Brdline(&n->br, '\n')) == nil){
+ werrstr("nntp eof");
+ return nil;
+ }
+ p[l=Blinelen(&n->br)-1] = '\0';
+ if(l > 0 && p[l-1] == '\r')
+ p[l-1] = '\0';
+if(netdebug)
+ fprint(2, "-> %s\n", p);
+ return p;
+}
+
+int
+nntpresponse(Netbuf *n, int e, char *cmd)
+{
+ int r;
+ char *p;
+
+ for(;;){
+ p = Nrdline(n);
+ if(p==nil){
+ strcpy(n->response, "early nntp eof");
+ return -1;
+ }
+ r = atoi(p);
+ if(r/100 == 1){ /* BUG? */
+ fprint(2, "%s\n", p);
+ continue;
+ }
+ break;
+ }
+
+ strecpy(n->response, n->response+sizeof(n->response), p);
+
+ if((r=atoi(p)) == 0){
+ close(n->fd);
+ n->fd = -1;
+ fprint(2, "bad nntp response: %s\n", p);
+ werrstr("bad nntp response");
+ return -1;
+ }
+
+ n->code = r;
+ if(0 < e && e<10 && r/100 != e){
+ fprint(2, "%s: expected %dxx: got %s\n", cmd, e, n->response);
+ return -1;
+ }
+ if(10 <= e && e<100 && r/10 != e){
+ fprint(2, "%s: expected %dx: got %s\n", cmd, e, n->response);
+ return -1;
+ }
+ if(100 <= e && r != e){
+ fprint(2, "%s: expected %d: got %s\n", cmd, e, n->response);
+ return -1;
+ }
+ return r;
+}
+
+int nntpauth(Netbuf*);
+int nntpxcmdprobe(Netbuf*);
+int nntpcurrentgroup(Netbuf*, Group*);
+
+/* XXX: bug OVER/XOVER et al. */
+static struct {
+ ulong n;
+ char *s;
+} extensions [] = {
+ { Nxover, "OVER" },
+ { Nxhdr, "HDR" },
+ { Nxpat, "PAT" },
+ { Nxlistgp, "LISTGROUP" },
+ { 0, nil }
+};
+
+static int indial;
+
+int
+nntpconnect(Netbuf *n)
+{
+ n->currentgroup = nil;
+ close(n->fd);
+ if((n->fd = dial(n->addr, nil, nil, nil)) < 0){
+ snprint(n->response, sizeof n->response, "dial: %r");
+ return -1;
+ }
+ Binit(&n->br, n->fd, OREAD);
+ Binit(&n->bw, n->fd, OWRITE);
+ if(nntpresponse(n, 20, "greeting") < 0)
+ return -1;
+ readonly = (n->code == 201);
+
+ indial = 1;
+ if(n->auth != 0)
+ nntpauth(n);
+// nntpxcmdprobe(n);
+ indial = 0;
+ return 0;
+}
+
+int
+nntpcmd(Netbuf *n, char *cmd, int e)
+{
+ int tried;
+
+ tried = 0;
+ for(;;){
+ if(netdebug)
+ fprint(2, "<- %s\n", cmd);
+ Bprint(&n->bw, "%s\r\n", cmd);
+ if(nntpresponse(n, e, cmd)>=0 && (e < 0 || n->code/100 != 5))
+ return 0;
+
+ /* redial */
+ if(indial || tried++ || nntpconnect(n) < 0)
+ return -1;
+ }
+}
+
+int
+nntpauth(Netbuf *n)
+{
+ char cmd[256];
+
+ snprint(cmd, sizeof cmd, "AUTHINFO USER %s", n->user);
+ if (nntpcmd(n, cmd, -1) < 0 || n->code != 381) {
+ fprint(2, "Authentication failed: %s\n", n->response);
+ return -1;
+ }
+
+ snprint(cmd, sizeof cmd, "AUTHINFO PASS %s", n->pass);
+ if (nntpcmd(n, cmd, -1) < 0 || n->code != 281) {
+ fprint(2, "Authentication failed: %s\n", n->response);
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+nntpxcmdprobe(Netbuf *n)
+{
+ int i;
+ char *p;
+
+ n->extended = 0;
+ if (nntpcmd(n, "LIST EXTENSIONS", 0) < 0 || n->code != 202)
+ return 0;
+
+ while((p = Nrdline(n)) != nil) {
+ if (strcmp(p, ".") == 0)
+ break;
+
+ for(i=0; extensions[i].s != nil; i++)
+ if (cistrcmp(extensions[i].s, p) == 0) {
+ n->extended |= extensions[i].n;
+ break;
+ }
+ }
+ return 0;
+}
+
+/* XXX: searching, lazy evaluation */
+static int
+overcmp(void *v1, void *v2)
+{
+ int a, b;
+
+ a = atoi(*(char**)v1);
+ b = atoi(*(char**)v2);
+
+ if(a < b)
+ return -1;
+ else if(a > b)
+ return 1;
+ return 0;
+}
+
+enum {
+ XoverChunk = 100,
+};
+
+char *xover[XoverChunk];
+int xoverlo;
+int xoverhi;
+int xovercount;
+Group *xovergroup;
+
+char*
+nntpover(Netbuf *n, Group *g, int m)
+{
+ int i, lo, hi, mid, msg;
+ char *p;
+ char cmd[64];
+
+ if (g->isgroup == 0) /* BUG: should check extension capabilities */
+ return nil;
+
+ if(g != xovergroup || m < xoverlo || m >= xoverhi){
+ lo = (m/XoverChunk)*XoverChunk;
+ hi = lo+XoverChunk;
+
+ if(lo < g->lo)
+ lo = g->lo;
+ else if (lo > g->hi)
+ lo = g->hi;
+ if(hi < lo || hi > g->hi)
+ hi = g->hi;
+
+ if(nntpcurrentgroup(n, g) < 0)
+ return nil;
+
+ if(lo == hi)
+ snprint(cmd, sizeof cmd, "XOVER %d", hi);
+ else
+ snprint(cmd, sizeof cmd, "XOVER %d-%d", lo, hi-1);
+ if(nntpcmd(n, cmd, 224) < 0)
+ return nil;
+
+ for(i=0; (p = Nrdline(n)) != nil; i++) {
+ if(strcmp(p, ".") == 0)
+ break;
+ if(i >= XoverChunk)
+ sysfatal("news server doesn't play by the rules");
+ free(xover[i]);
+ xover[i] = emalloc(strlen(p)+2);
+ strcpy(xover[i], p);
+ strcat(xover[i], "\n");
+ }
+ qsort(xover, i, sizeof(xover[0]), overcmp);
+
+ xovercount = i;
+
+ xovergroup = g;
+ xoverlo = lo;
+ xoverhi = hi;
+ }
+
+ lo = 0;
+ hi = xovercount;
+ /* search for message */
+ while(lo < hi){
+ mid = (lo+hi)/2;
+ msg = atoi(xover[mid]);
+ if(m == msg)
+ return xover[mid];
+ else if(m < msg)
+ hi = mid;
+ else
+ lo = mid+1;
+ }
+ return nil;
+}
+
+/*
+ * Return the new Group structure for the group name.
+ * Destroys name.
+ */
+static int printgroup(char*,Group*);
+Group*
+findgroup(Group *g, char *name, int mk)
+{
+ int lo, hi, m;
+ char *p, *q;
+ static int ngroup;
+
+ for(p=name; *p; p=q){
+ if(q = strchr(p, '.'))
+ *q++ = '\0';
+ else
+ q = p+strlen(p);
+
+ lo = 0;
+ hi = g->nkid;
+ while(hi-lo > 1){
+ m = (lo+hi)/2;
+ if(strcmp(p, g->kid[m]->name) < 0)
+ hi = m;
+ else
+ lo = m;
+ }
+ assert(lo==hi || lo==hi-1);
+ if(lo==hi || strcmp(p, g->kid[lo]->name) != 0){
+ if(mk==0)
+ return nil;
+ if(g->nkid%16 == 0)
+ g->kid = erealloc(g->kid, (g->nkid+16)*sizeof(g->kid[0]));
+
+ /*
+ * if we're down to a single place 'twixt lo and hi, the insertion might need
+ * to go at lo or at hi. strcmp to find out. the list needs to stay sorted.
+ */
+ if(lo==hi-1 && strcmp(p, g->kid[lo]->name) < 0)
+ hi = lo;
+
+ if(hi < g->nkid)
+ memmove(g->kid+hi+1, g->kid+hi, sizeof(g->kid[0])*(g->nkid-hi));
+ g->nkid++;
+ g->kid[hi] = emalloc(sizeof(*g));
+ g->kid[hi]->parent = g;
+ g = g->kid[hi];
+ g->name = estrdup(p);
+ g->num = ++ngroup;
+ g->mtime = time(0);
+ }else
+ g = g->kid[lo];
+ }
+ if(mk)
+ g->isgroup = 1;
+ return g;
+}
+
+static int
+printgroup(char *s, Group *g)
+{
+ if(g->parent == g)
+ return 0;
+
+ if(printgroup(s, g->parent))
+ strcat(s, ".");
+ strcat(s, g->name);
+ return 1;
+}
+
+static char*
+Nreaddata(Netbuf *n)
+{
+ char *p, *q;
+ int l;
+
+ p = nil;
+ l = 0;
+ for(;;){
+ q = Nrdline(n);
+ if(q==nil){
+ free(p);
+ return nil;
+ }
+ if(strcmp(q, ".")==0)
+ return p;
+ if(q[0]=='.')
+ q++;
+ p = erealloc(p, l+strlen(q)+1+1);
+ strcpy(p+l, q);
+ strcat(p+l, "\n");
+ l += strlen(p+l);
+ }
+}
+
+/*
+ * Return the output of a HEAD, BODY, or ARTICLE command.
+ */
+char*
+nntpget(Netbuf *n, Group *g, int msg, char *retr)
+{
+ char *s;
+ char cmd[1024];
+
+ if(g->isgroup == 0){
+ werrstr("not a group");
+ return nil;
+ }
+
+ if(strcmp(retr, "XOVER") == 0){
+ s = nntpover(n, g, msg);
+ if(s == nil)
+ s = "";
+ return estrdup(s);
+ }
+
+ if(nntpcurrentgroup(n, g) < 0)
+ return nil;
+ sprint(cmd, "%s %d", retr, msg);
+ nntpcmd(n, cmd, 0);
+ if(n->code/10 != 22)
+ return nil;
+
+ return Nreaddata(n);
+}
+
+int
+nntpcurrentgroup(Netbuf *n, Group *g)
+{
+ char cmd[1024];
+
+ if(n->currentgroup != g){
+ strcpy(cmd, "GROUP ");
+ printgroup(cmd, g);
+ if(nntpcmd(n, cmd, 21) < 0)
+ return -1;
+ n->currentgroup = g;
+ }
+ return 0;
+}
+
+void
+nntprefreshall(Netbuf *n)
+{
+ char *f[10], *p;
+ int hi, lo, nf;
+ Group *g;
+
+ if(nntpcmd(n, "LIST", 21) < 0)
+ return;
+
+ while(p = Nrdline(n)){
+ if(strcmp(p, ".")==0)
+ break;
+
+ nf = getfields(p, f, nelem(f), 1, "\t\r\n ");
+ if(nf != 4){
+ int i;
+ for(i=0; i<nf; i++)
+ fprint(2, "%s%s", i?" ":"", f[i]);
+ fprint(2, "\n");
+ fprint(2, "syntax error in group list, line %d", n->lineno);
+ return;
+ }
+ g = findgroup(root, f[0], 1);
+ hi = strtol(f[1], 0, 10)+1;
+ lo = strtol(f[2], 0, 10);
+ if(g->hi != hi){
+ g->hi = hi;
+ if(g->lo==0)
+ g->lo = lo;
+ g->canpost = f[3][0] == 'y';
+ g->mtime = time(0);
+ }
+ }
+}
+
+void
+nntprefresh(Netbuf *n, Group *g)
+{
+ char cmd[1024];
+ char *f[5];
+ int lo, hi;
+
+ if(g->isgroup==0)
+ return;
+
+ if(time(0) - g->atime < 30)
+ return;
+
+ strcpy(cmd, "GROUP ");
+ printgroup(cmd, g);
+ if(nntpcmd(n, cmd, 21) < 0){
+ n->currentgroup = nil;
+ return;
+ }
+ n->currentgroup = g;
+
+ if(tokenize(n->response, f, nelem(f)) < 4){
+ fprint(2, "error reading GROUP response");
+ return;
+ }
+
+ /* backwards from LIST! */
+ hi = strtol(f[3], 0, 10)+1;
+ lo = strtol(f[2], 0, 10);
+ if(g->hi != hi){
+ g->mtime = time(0);
+ if(g->lo==0)
+ g->lo = lo;
+ g->hi = hi;
+ }
+ g->atime = time(0);
+}
+
+char*
+nntppost(Netbuf *n, char *msg)
+{
+ char *p, *q;
+
+ if(nntpcmd(n, "POST", 34) < 0)
+ return n->response;
+
+ for(p=msg; *p; p=q){
+ if(q = strchr(p, '\n'))
+ *q++ = '\0';
+ else
+ q = p+strlen(p);
+
+ if(p[0]=='.')
+ Bputc(&n->bw, '.');
+ Bwrite(&n->bw, p, strlen(p));
+ Bputc(&n->bw, '\r');
+ Bputc(&n->bw, '\n');
+ }
+ Bprint(&n->bw, ".\r\n");
+
+ if(nntpresponse(n, 0, nil) < 0)
+ return n->response;
+
+ if(n->code/100 != 2)
+ return n->response;
+ return nil;
+}
+
+/*
+ * Because an expanded QID space makes thngs much easier,
+ * we sleazily use the version part of the QID as more path bits.
+ * Since we make sure not to mount ourselves cached, this
+ * doesn't break anything (unless you want to bind on top of
+ * things in this file system). In the next version of 9P, we'll
+ * have more QID bits to play with.
+ *
+ * The newsgroup is encoded in the top 15 bits
+ * of the path. The message number is the bottom 17 bits.
+ * The file within the message directory is in the version [sic].
+ */
+
+enum { /* file qids */
+ Qhead,
+ Qbody,
+ Qarticle,
+ Qxover,
+ Nfile,
+};
+char *filename[] = {
+ "header",
+ "body",
+ "article",
+ "xover",
+};
+char *nntpname[] = {
+ "HEAD",
+ "BODY",
+ "ARTICLE",
+ "XOVER",
+};
+
+#define GROUP(p) (((p)>>17)&0x3FFF)
+#define MESSAGE(p) ((p)&0x1FFFF)
+#define FILE(v) ((v)&0x3)
+
+#define PATH(g,m) ((((g)&0x3FFF)<<17)|((m)&0x1FFFF))
+#define POST(g) PATH(0,g,0)
+#define VERS(f) ((f)&0x3)
+
+typedef struct Aux Aux;
+struct Aux {
+ Group *g;
+ int n;
+ int ispost;
+ int file;
+ char *s;
+ int ns;
+ int offset;
+};
+
+static void
+fsattach(Req *r)
+{
+ Aux *a;
+ char *spec;
+
+ spec = r->ifcall.aname;
+ if(spec && spec[0]){
+ respond(r, "invalid attach specifier");
+ return;
+ }
+
+ a = emalloc(sizeof *a);
+ a->g = root;
+ a->n = -1;
+ r->fid->aux = a;
+
+ r->ofcall.qid = (Qid){0, 0, QTDIR};
+ r->fid->qid = r->ofcall.qid;
+ respond(r, nil);
+}
+
+static char*
+fsclone(Fid *ofid, Fid *fid)
+{
+ Aux *a;
+
+ a = emalloc(sizeof(*a));
+ *a = *(Aux*)ofid->aux;
+ fid->aux = a;
+ return nil;
+}
+
+static char*
+fswalk1(Fid *fid, char *name, Qid *qid)
+{
+ char *p;
+ int i, isdotdot, n;
+ Aux *a;
+ Group *ng;
+
+ isdotdot = strcmp(name, "..")==0;
+
+ a = fid->aux;
+ if(a->s) /* file */
+ return "protocol botch";
+ if(a->n != -1){
+ if(isdotdot){
+ *qid = (Qid){PATH(a->g->num, 0), 0, QTDIR};
+ fid->qid = *qid;
+ a->n = -1;
+ return nil;
+ }
+ for(i=0; i<Nfile; i++){
+ if(strcmp(name, filename[i])==0){
+ if(a->s = nntpget(net, a->g, a->n, nntpname[i])){
+ *qid = (Qid){PATH(a->g->num, a->n), Qbody, 0};
+ fid->qid = *qid;
+ a->file = i;
+ return nil;
+ }else
+ return "file does not exist";
+ }
+ }
+ return "file does not exist";
+ }
+
+ if(isdotdot){
+ a->g = a->g->parent;
+ *qid = (Qid){PATH(a->g->num, 0), 0, QTDIR};
+ fid->qid = *qid;
+ return nil;
+ }
+
+ if(a->g->isgroup && !readonly && a->g->canpost
+ && strcmp(name, "post")==0){
+ a->ispost = 1;
+ *qid = (Qid){PATH(a->g->num, 0), 0, 0};
+ fid->qid = *qid;
+ return nil;
+ }
+
+ if(ng = findgroup(a->g, name, 0)){
+ a->g = ng;
+ *qid = (Qid){PATH(a->g->num, 0), 0, QTDIR};
+ fid->qid = *qid;
+ return nil;
+ }
+
+ n = strtoul(name, &p, 0);
+ if('0'<=name[0] && name[0]<='9' && *p=='\0' && a->g->lo<=n && n<a->g->hi){
+ a->n = n;
+ *qid = (Qid){PATH(a->g->num, n+1-a->g->lo), 0, QTDIR};
+ fid->qid = *qid;
+ return nil;
+ }
+
+ return "file does not exist";
+}
+
+static void
+fsopen(Req *r)
+{
+ Aux *a;
+
+ a = r->fid->aux;
+ if((a->ispost && (r->ifcall.mode&~OTRUNC) != OWRITE)
+ || (!a->ispost && r->ifcall.mode != OREAD))
+ respond(r, "permission denied");
+ else
+ respond(r, nil);
+}
+
+static void
+fillstat(Dir *d, Aux *a)
+{
+ char buf[32];
+ Group *g;
+
+ memset(d, 0, sizeof *d);
+ d->uid = estrdup("nntp");
+ d->gid = estrdup("nntp");
+ g = a->g;
+ d->atime = d->mtime = g->mtime;
+
+ if(a->ispost){
+ d->name = estrdup("post");
+ d->mode = 0222;
+ d->qid = (Qid){PATH(g->num, 0), 0, 0};
+ d->length = a->ns;
+ return;
+ }
+
+ if(a->s){ /* article file */
+ d->name = estrdup(filename[a->file]);
+ d->mode = 0444;
+ d->qid = (Qid){PATH(g->num, a->n+1-g->lo), a->file, 0};
+ return;
+ }
+
+ if(a->n != -1){ /* article directory */
+ sprint(buf, "%d", a->n);
+ d->name = estrdup(buf);
+ d->mode = DMDIR|0555;
+ d->qid = (Qid){PATH(g->num, a->n+1-g->lo), 0, QTDIR};
+ return;
+ }
+
+ /* group directory */
+ if(g->name[0])
+ d->name = estrdup(g->name);
+ else
+ d->name = estrdup("/");
+ d->mode = DMDIR|0555;
+ d->qid = (Qid){PATH(g->num, 0), g->hi-1, QTDIR};
+}
+
+static int
+dirfillstat(Dir *d, Aux *a, int i)
+{
+ int ndir;
+ Group *g;
+ char buf[32];
+
+ memset(d, 0, sizeof *d);
+ d->uid = estrdup("nntp");
+ d->gid = estrdup("nntp");
+
+ g = a->g;
+ d->atime = d->mtime = g->mtime;
+
+ if(a->n != -1){ /* article directory */
+ if(i >= Nfile)
+ return -1;
+
+ d->name = estrdup(filename[i]);
+ d->mode = 0444;
+ d->qid = (Qid){PATH(g->num, a->n), i, 0};
+ return 0;
+ }
+
+ /* hierarchy directory: child groups */
+ if(i < g->nkid){
+ d->name = estrdup(g->kid[i]->name);
+ d->mode = DMDIR|0555;
+ d->qid = (Qid){PATH(g->kid[i]->num, 0), g->kid[i]->hi-1, QTDIR};
+ return 0;
+ }
+ i -= g->nkid;
+
+ /* group directory: post file */
+ if(g->isgroup && !readonly && g->canpost){
+ if(i < 1){
+ d->name = estrdup("post");
+ d->mode = 0222;
+ d->qid = (Qid){PATH(g->num, 0), 0, 0};
+ return 0;
+ }
+ i--;
+ }
+
+ /* group directory: child articles */
+ ndir = g->hi - g->lo;
+ if(i < ndir){
+ sprint(buf, "%d", g->lo+i);
+ d->name = estrdup(buf);
+ d->mode = DMDIR|0555;
+ d->qid = (Qid){PATH(g->num, i+1), 0, QTDIR};
+ return 0;
+ }
+
+ return -1;
+}
+
+static void
+fsstat(Req *r)
+{
+ Aux *a;
+
+ a = r->fid->aux;
+ if(r->fid->qid.path == 0 && (r->fid->qid.type & QTDIR))
+ nntprefreshall(net);
+ else if(a->g->isgroup)
+ nntprefresh(net, a->g);
+ fillstat(&r->d, a);
+ respond(r, nil);
+}
+
+static void
+fsread(Req *r)
+{
+ int offset, n;
+ Aux *a;
+ char *p, *ep;
+ Dir d;
+
+ a = r->fid->aux;
+ if(a->s){
+ readstr(r, a->s);
+ respond(r, nil);
+ return;
+ }
+
+ if(r->ifcall.offset == 0)
+ offset = 0;
+ else
+ offset = a->offset;
+
+ p = r->ofcall.data;
+ ep = r->ofcall.data+r->ifcall.count;
+ for(; p+2 < ep; p += n){
+ if(dirfillstat(&d, a, offset) < 0)
+ break;
+ n=convD2M(&d, (uchar*)p, ep-p);
+ free(d.name);
+ free(d.uid);
+ free(d.gid);
+ free(d.muid);
+ if(n <= BIT16SZ)
+ break;
+ offset++;
+ }
+ a->offset = offset;
+ r->ofcall.count = p - r->ofcall.data;
+ respond(r, nil);
+}
+
+static void
+fswrite(Req *r)
+{
+ Aux *a;
+ long count;
+ vlong offset;
+
+ a = r->fid->aux;
+
+ if(r->ifcall.count == 0){ /* commit */
+ respond(r, nntppost(net, a->s));
+ free(a->s);
+ a->ns = 0;
+ a->s = nil;
+ return;
+ }
+
+ count = r->ifcall.count;
+ offset = r->ifcall.offset;
+ if(a->ns < count+offset+1){
+ a->s = erealloc(a->s, count+offset+1);
+ a->ns = count+offset;
+ a->s[a->ns] = '\0';
+ }
+ memmove(a->s+offset, r->ifcall.data, count);
+ r->ofcall.count = count;
+ respond(r, nil);
+}
+
+static void
+fsdestroyfid(Fid *fid)
+{
+ Aux *a;
+
+ a = fid->aux;
+ if(a==nil)
+ return;
+
+ if(a->ispost && a->s)
+ nntppost(net, a->s);
+
+ free(a->s);
+ free(a);
+}
+
+Srv nntpsrv = {
+.destroyfid= fsdestroyfid,
+.attach= fsattach,
+.clone= fsclone,
+.walk1= fswalk1,
+.open= fsopen,
+.read= fsread,
+.write= fswrite,
+.stat= fsstat,
+};
+
+void
+usage(void)
+{
+ fprint(2, "usage: nntpsrv [-a] [-s service] [-m mtpt] [nntp.server]\n");
+ exits("usage");
+}
+
+void
+dumpgroups(Group *g, int ind)
+{
+ int i;
+
+ print("%*s%s\n", ind*4, "", g->name);
+ for(i=0; i<g->nkid; i++)
+ dumpgroups(g->kid[i], ind+1);
+}
+
+void
+main(int argc, char **argv)
+{
+ int auth, x;
+ char *mtpt, *service, *where, *user;
+ Netbuf n;
+ UserPasswd *up;
+
+ mtpt = "/mnt/news";
+ service = nil;
+ memset(&n, 0, sizeof n);
+ user = nil;
+ auth = 0;
+ ARGBEGIN{
+ case 'D':
+ chatty9p++;
+ break;
+ case 'N':
+ netdebug = 1;
+ break;
+ case 'a':
+ auth = 1;
+ break;
+ case 'u':
+ user = EARGF(usage());
+ break;
+ case 's':
+ service = EARGF(usage());
+ break;
+ case 'm':
+ mtpt = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc > 1)
+ usage();
+ if(argc==0)
+ where = "$nntp";
+ else
+ where = argv[0];
+
+ now = time(0);
+
+ net = &n;
+ if(auth) {
+ n.auth = 1;
+ if(user)
+ up = auth_getuserpasswd(auth_getkey, "proto=pass service=nntp server=%q user=%q", where, user);
+ else
+ up = auth_getuserpasswd(auth_getkey, "proto=pass service=nntp server=%q", where);
+ if(up == nil)
+ sysfatal("no password: %r");
+
+ n.user = up->user;
+ n.pass = up->passwd;
+ }
+
+ n.addr = netmkaddr(where, "tcp", "nntp");
+
+ root = emalloc(sizeof *root);
+ root->name = estrdup("");
+ root->parent = root;
+
+ n.fd = -1;
+ if(nntpconnect(&n) < 0)
+ sysfatal("nntpconnect: %s", n.response);
+
+ x=netdebug;
+ netdebug=0;
+ nntprefreshall(&n);
+ netdebug=x;
+// dumpgroups(root, 0);
+
+ postmountsrv(&nntpsrv, service, mtpt, MREPL);
+ exits(nil);
+}
+