/*
* 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);
}