<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv=Content-Type content="text/html; charset=utf8">
<title>/usr/web/sources/contrib/cinap_lenrek/nntpfs.c - Plan 9 from Bell Labs</title>
<!-- THIS FILE IS AUTOMATICALLY GENERATED. -->
<!-- EDIT sources.tr INSTEAD. -->
</meta>
</head>
<body>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="line-height: 1.2em; margin-left: 1.00in; text-indent: 0.00in; margin-right: 1.00in; margin-top: 0; margin-bottom: 0; text-align: center;">
<span style="font-size: 10pt"><a href="/plan9/">Plan 9 from Bell Labs</a>&rsquo;s /usr/web/sources/contrib/cinap_lenrek/nntpfs.c</span></p>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center><font size=-1>
Copyright © 2009 Alcatel-Lucent.<br />
Distributed under the
<a href="/plan9/license.html">Lucent Public License version 1.02</a>.
<br />
<a href="/plan9/download.html">Download the Plan 9 distribution.</a>
</font>
</center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<table width="100%" cellspacing=0 border=0><tr><td align="center">
<table cellspacing=0 cellpadding=5 bgcolor="#eeeeff"><tr><td align="left">
<pre>
<!-- END HEADER -->
/*
* 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 &lt;u.h&gt;
#include &lt;libc.h&gt;
#include &lt;bio.h&gt;
#include &lt;auth.h&gt;
#include &lt;fcall.h&gt;
#include &lt;thread.h&gt;
#include &lt;9p.h&gt;

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&lt;&lt;0),
       Nxhdr    = (1&lt;&lt;1),
       Nxpat    = (1&lt;&lt;2),
       Nxlistgp = (1&lt;&lt;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(&amp;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(&amp;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(&amp;s));
       return t;
}

char*
estrdupn(char *s, int n)
{
       int l;
       char *t;

       l = strlen(s);
       if(l &gt; n)
               l = n;
       t = emalloc(l+1);
       memmove(t, s, l);
       t[l] = '\0';
       setmalloctag(t, getcallerpc(&amp;s));
       return t;
}

static char base64[] =
       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

char*
decode64(char *s)
{
       int state;

       char c;
       char *d;
       char *dst;

       dst = d = malloc(strlen(s)+1);

       state = 0;

       while(c = *s++){
               char *p;

               if(strchr("\n\r         ", c))
                       continue;
               if(c == '=')
                       break;

               if((p = strchr(base64, c)) == 0)
                       goto errout;

               switch(state){
               case 0:
                       d[0] = ((p - base64) &lt;&lt; 2);
                       state = 1;
                       break;
               case 1:
                       d[0] |= ((p - base64) &gt;&gt; 4);
                       d[1] = (((p - base64) &amp; 0x0f) &lt;&lt; 4);
                       d++;
                       state = 2;
                       break;
               case 2:
                       d[0] |= ((p - base64) &gt;&gt; 2);
                       d[1] = (((p - base64) &amp; 0x03) &lt;&lt; 6);
                       d++;
                       state = 3;
                       break;
               case 3:
                       d[0] |= (p - base64);
                       d++;
                       state = 0;
                       break;
               }
       }
       *++d = 0;
       return dst;

errout:
       free(dst);
       return nil;
}

void
convertarticle(char *data)
{
       char *s;
       int isb64;

       s = data;
       isb64 = 0;
       while(*s){
               if(*s == '\n')
                       break;
               if(strstr(s, "Content-Transfer-Encoding: base64") == s)
                       isb64 = 1;
               while(*s &amp;&amp; *s != '\n')
                       s++;
               if(*s)
                       s++;
       }
       if(*s++ == '\n' &amp;&amp; isb64){
               char *x;
               if(x = decode64(s)){
                       strcpy(s, x);
                       free(x);
               }
       }
}

char*
Nrdline(Netbuf *n)
{
       char *p;
       int l;

       n-&gt;lineno++;
       Bflush(&amp;n-&gt;bw);
       if((p = Brdline(&amp;n-&gt;br, '\n')) == nil){
               werrstr("nntp eof");
               return nil;
       }
       p[l=Blinelen(&amp;n-&gt;br)-1] = '\0';
       if(l &gt; 0 &amp;&amp; p[l-1] == '\r')
               p[l-1] = '\0';
if(netdebug)
       fprint(2, "-&gt; %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-&gt;response, "early nntp eof");
                       return -1;
               }
               r = atoi(p);
               if(r/100 == 1){ /* BUG? */
                       fprint(2, "%s\n", p);
                       continue;
               }
               break;
       }

       strecpy(n-&gt;response, n-&gt;response+sizeof(n-&gt;response), p);

       if((r=atoi(p)) == 0){
               close(n-&gt;fd);
               n-&gt;fd = -1;
               fprint(2, "bad nntp response: %s\n", p);
               werrstr("bad nntp response");
               return -1;
       }

       n-&gt;code = r;
       if(0 &lt; e &amp;&amp; e&lt;10 &amp;&amp; r/100 != e){
               fprint(2, "%s: expected %dxx: got %s\n", cmd, e, n-&gt;response);
               return -1;
       }
       if(10 &lt;= e &amp;&amp; e&lt;100 &amp;&amp; r/10 != e){
               fprint(2, "%s: expected %dx: got %s\n", cmd, e, n-&gt;response);
               return -1;
       }
       if(100 &lt;= e &amp;&amp; r != e){
               fprint(2, "%s: expected %d: got %s\n", cmd, e, n-&gt;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-&gt;currentgroup = nil;
       close(n-&gt;fd);
       if((n-&gt;fd = dial(n-&gt;addr, nil, nil, nil)) &lt; 0){
               snprint(n-&gt;response, sizeof n-&gt;response, "dial: %r");
               return -1;
       }
       Binit(&amp;n-&gt;br, n-&gt;fd, OREAD);
       Binit(&amp;n-&gt;bw, n-&gt;fd, OWRITE);
       if(nntpresponse(n, 20, "greeting") &lt; 0)
               return -1;
       readonly = (n-&gt;code == 201);

       indial = 1;
       if(n-&gt;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, "&lt;- %s\n", cmd);
               Bprint(&amp;n-&gt;bw, "%s\r\n", cmd);
               if(nntpresponse(n, e, cmd)&gt;=0 &amp;&amp; (e &lt; 0 || n-&gt;code/100 != 5))
                       return 0;

               /* redial */
               if(indial || tried++ || nntpconnect(n) &lt; 0)
                       return -1;
       }
}

int
nntpauth(Netbuf *n)
{
       char cmd[256];

       snprint(cmd, sizeof cmd, "AUTHINFO USER %s", n-&gt;user);
       if (nntpcmd(n, cmd, -1) &lt; 0 || n-&gt;code != 381) {
               fprint(2, "Authentication failed: %s\n", n-&gt;response);
               return -1;
       }

       snprint(cmd, sizeof cmd, "AUTHINFO PASS %s", n-&gt;pass);
       if (nntpcmd(n, cmd, -1) &lt; 0 || n-&gt;code != 281) {
               fprint(2, "Authentication failed: %s\n", n-&gt;response);
               return -1;
       }

       return 0;
}

int
nntpxcmdprobe(Netbuf *n)
{
       int i;
       char *p;

       n-&gt;extended = 0;
       if (nntpcmd(n, "LIST EXTENSIONS", 0) &lt; 0 || n-&gt;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-&gt;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 &lt; b)
               return -1;
       else if(a &gt; 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-&gt;isgroup == 0) /* BUG: should check extension capabilities */
               return nil;

       if(g != xovergroup || m &lt; xoverlo || m &gt;= xoverhi){
               lo = (m/XoverChunk)*XoverChunk;
               hi = lo+XoverChunk;

               if(lo &lt; g-&gt;lo)
                       lo = g-&gt;lo;
               else if (lo &gt; g-&gt;hi)
                       lo = g-&gt;hi;
               if(hi &lt; lo || hi &gt; g-&gt;hi)
                       hi = g-&gt;hi;

               if(nntpcurrentgroup(n, g) &lt; 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) &lt; 0)
                       return nil;

               for(i=0; (p = Nrdline(n)) != nil; i++) {
                       if(strcmp(p, ".") == 0)
                               break;
                       if(i &gt;= 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 &lt; hi){
               mid = (lo+hi)/2;
               msg = atoi(xover[mid]);
               if(m == msg)
                       return xover[mid];
               else if(m &lt; 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-&gt;nkid;
               while(hi-lo &gt; 1){
                       m = (lo+hi)/2;
                       if(strcmp(p, g-&gt;kid[m]-&gt;name) &lt; 0)
                               hi = m;
                       else
                               lo = m;
               }
               assert(lo==hi || lo==hi-1);
               if(lo==hi || strcmp(p, g-&gt;kid[lo]-&gt;name) != 0){
                       if(mk==0)
                               return nil;
                       if(g-&gt;nkid%16 == 0)
                               g-&gt;kid = erealloc(g-&gt;kid, (g-&gt;nkid+16)*sizeof(g-&gt;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 &amp;&amp; strcmp(p, g-&gt;kid[lo]-&gt;name) &lt; 0)
                               hi = lo;

                       if(hi &lt; g-&gt;nkid)
                               memmove(g-&gt;kid+hi+1, g-&gt;kid+hi, sizeof(g-&gt;kid[0])*(g-&gt;nkid-hi));
                       g-&gt;nkid++;
                       g-&gt;kid[hi] = emalloc(sizeof(*g));
                       g-&gt;kid[hi]-&gt;parent = g;
                       g = g-&gt;kid[hi];
                       g-&gt;name = estrdup(p);
                       g-&gt;num = ++ngroup;
                       g-&gt;mtime = time(0);
               }else
                       g = g-&gt;kid[lo];
       }
       if(mk)
               g-&gt;isgroup = 1;
       return g;
}

static int
printgroup(char *s, Group *g)
{
       if(g-&gt;parent == g)
               return 0;

       if(printgroup(s, g-&gt;parent))
               strcat(s, ".");
       strcat(s, g-&gt;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-&gt;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) &lt; 0)
               return nil;
       sprint(cmd, "%s %d", retr, msg);
       nntpcmd(n, cmd, 0);
       if(n-&gt;code/10 != 22)
               return nil;

       s = Nreaddata(n);
       if(strcmp(retr, "ARTICLE") == 0)
               convertarticle(s);

       return s;
}

int
nntpcurrentgroup(Netbuf *n, Group *g)
{
       char cmd[1024];

       if(n-&gt;currentgroup != g){
               strcpy(cmd, "GROUP ");
               printgroup(cmd, g);
               if(nntpcmd(n, cmd, 21) &lt; 0)
                       return -1;
               n-&gt;currentgroup = g;
       }
       return 0;
}

void
nntprefreshall(Netbuf *n)
{
       char *f[10], *p;
       int hi, lo, nf;
       Group *g;

       if(nntpcmd(n, "LIST", 21) &lt; 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&lt;nf; i++)
                               fprint(2, "%s%s", i?" ":"", f[i]);
                       fprint(2, "\n");
                       fprint(2, "syntax error in group list, line %d", n-&gt;lineno);
                       return;
               }
               g = findgroup(root, f[0], 1);
               hi = strtol(f[1], 0, 10)+1;
               lo = strtol(f[2], 0, 10);
               if(g-&gt;hi != hi){
                       g-&gt;hi = hi;
                       if(g-&gt;lo==0)
                               g-&gt;lo = lo;
                       g-&gt;canpost = f[3][0] == 'y';
                       g-&gt;mtime = time(0);
               }
       }
}

void
nntprefresh(Netbuf *n, Group *g)
{
       char cmd[1024];
       char *f[5];
       int lo, hi;

       if(g-&gt;isgroup==0)
               return;

       if(time(0) - g-&gt;atime &lt; 30)
               return;

       strcpy(cmd, "GROUP ");
       printgroup(cmd, g);
       if(nntpcmd(n, cmd, 21) &lt; 0){
               n-&gt;currentgroup = nil;
               return;
       }
       n-&gt;currentgroup = g;

       if(tokenize(n-&gt;response, f, nelem(f)) &lt; 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-&gt;hi != hi){
               g-&gt;mtime = time(0);
               if(g-&gt;lo==0)
                       g-&gt;lo = lo;
               g-&gt;hi = hi;
       }
       g-&gt;atime = time(0);
}

char*
nntppost(Netbuf *n, char *msg)
{
       char *p, *q;

       if(nntpcmd(n, "POST", 34) &lt; 0)
               return n-&gt;response;

       for(p=msg; *p; p=q){
               if(q = strchr(p, '\n'))
                       *q++ = '\0';
               else
                       q = p+strlen(p);

               if(p[0]=='.')
                       Bputc(&amp;n-&gt;bw, '.');
               Bwrite(&amp;n-&gt;bw, p, strlen(p));
               Bputc(&amp;n-&gt;bw, '\r');
               Bputc(&amp;n-&gt;bw, '\n');
       }
       Bprint(&amp;n-&gt;bw, ".\r\n");

       if(nntpresponse(n, 0, nil) &lt; 0)
               return n-&gt;response;

       if(n-&gt;code/100 != 2)
               return n-&gt;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)&gt;&gt;17)&amp;0x3FFF)
#define MESSAGE(p)      ((p)&amp;0x1FFFF)
#define FILE(v)         ((v)&amp;0x3)

#define PATH(g,m)       ((((g)&amp;0x3FFF)&lt;&lt;17)|((m)&amp;0x1FFFF))
#define POST(g) PATH(0,g,0)
#define VERS(f)         ((f)&amp;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-&gt;ifcall.aname;
       if(spec &amp;&amp; spec[0]){
               respond(r, "invalid attach specifier");
               return;
       }

       a = emalloc(sizeof *a);
       a-&gt;g = root;
       a-&gt;n = -1;
       r-&gt;fid-&gt;aux = a;

       r-&gt;ofcall.qid = (Qid){0, 0, QTDIR};
       r-&gt;fid-&gt;qid = r-&gt;ofcall.qid;
       respond(r, nil);
}

static char*
fsclone(Fid *ofid, Fid *fid)
{
       Aux *a;

       a = emalloc(sizeof(*a));
       *a = *(Aux*)ofid-&gt;aux;
       fid-&gt;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-&gt;aux;
       if(a-&gt;s)     /* file */
               return "protocol botch";
       if(a-&gt;n != -1){
               if(isdotdot){
                       *qid = (Qid){PATH(a-&gt;g-&gt;num, 0), 0, QTDIR};
                       fid-&gt;qid = *qid;
                       a-&gt;n = -1;
                       return nil;
               }
               for(i=0; i&lt;Nfile; i++){
                       if(strcmp(name, filename[i])==0){
                               if(a-&gt;s = nntpget(net, a-&gt;g, a-&gt;n, nntpname[i])){
                                       if(i == Qbody){
                                               char *x;

                                               if(x = nntpget(net, a-&gt;g, a-&gt;n, nntpname[Qhead])){
                                                       if(strstr(x, "Content-Transfer-Encoding: base64")){
                                                               char *t;
                                                               if(t = decode64(a-&gt;s)){
                                                                       free(a-&gt;s);
                                                                       a-&gt;s = t;
                                                               }
                                                       }
                                                       free(x);
                                               }
                                       }

                                       *qid = (Qid){PATH(a-&gt;g-&gt;num, a-&gt;n), Qbody, 0};
                                       fid-&gt;qid = *qid;
                                       a-&gt;file = i;
                                       return nil;
                               }else
                                       return "file does not exist";
                       }
               }
               return "file does not exist";
       }

       if(isdotdot){
               a-&gt;g = a-&gt;g-&gt;parent;
               *qid = (Qid){PATH(a-&gt;g-&gt;num, 0), 0, QTDIR};
               fid-&gt;qid = *qid;
               return nil;
       }

       if(a-&gt;g-&gt;isgroup &amp;&amp; !readonly &amp;&amp; a-&gt;g-&gt;canpost
       &amp;&amp; strcmp(name, "post")==0){
               a-&gt;ispost = 1;
               *qid = (Qid){PATH(a-&gt;g-&gt;num, 0), 0, 0};
               fid-&gt;qid = *qid;
               return nil;
       }

       if(ng = findgroup(a-&gt;g, name, 0)){
               a-&gt;g = ng;
               *qid = (Qid){PATH(a-&gt;g-&gt;num, 0), 0, QTDIR};
               fid-&gt;qid = *qid;
               return nil;
       }

       n = strtoul(name, &amp;p, 0);
       if('0'&lt;=name[0] &amp;&amp; name[0]&lt;='9' &amp;&amp; *p=='\0' &amp;&amp; a-&gt;g-&gt;lo&lt;=n &amp;&amp; n&lt;a-&gt;g-&gt;hi){
               a-&gt;n = n;
               *qid = (Qid){PATH(a-&gt;g-&gt;num, n+1-a-&gt;g-&gt;lo), 0, QTDIR};
               fid-&gt;qid = *qid;
               return nil;
       }

       return "file does not exist";
}

static void
fsopen(Req *r)
{
       Aux *a;

       a = r-&gt;fid-&gt;aux;
       if((a-&gt;ispost &amp;&amp; (r-&gt;ifcall.mode&amp;~OTRUNC) != OWRITE)
       || (!a-&gt;ispost &amp;&amp; r-&gt;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-&gt;uid = estrdup("nntp");
       d-&gt;gid = estrdup("nntp");
       g = a-&gt;g;
       d-&gt;atime = d-&gt;mtime = g-&gt;mtime;

       if(a-&gt;ispost){
               d-&gt;name = estrdup("post");
               d-&gt;mode = 0222;
               d-&gt;qid = (Qid){PATH(g-&gt;num, 0), 0, 0};
               d-&gt;length = a-&gt;ns;
               return;
       }

       if(a-&gt;s){    /* article file */
               d-&gt;name = estrdup(filename[a-&gt;file]);
               d-&gt;mode = 0444;
               d-&gt;qid = (Qid){PATH(g-&gt;num, a-&gt;n+1-g-&gt;lo), a-&gt;file, 0};
               return;
       }

       if(a-&gt;n != -1){      /* article directory */
               sprint(buf, "%d", a-&gt;n);
               d-&gt;name = estrdup(buf);
               d-&gt;mode = DMDIR|0555;
               d-&gt;qid = (Qid){PATH(g-&gt;num, a-&gt;n+1-g-&gt;lo), 0, QTDIR};
               return;
       }

       /* group directory */
       if(g-&gt;name[0])
               d-&gt;name = estrdup(g-&gt;name);
       else
               d-&gt;name = estrdup("/");
       d-&gt;mode = DMDIR|0555;
       d-&gt;qid = (Qid){PATH(g-&gt;num, 0), g-&gt;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-&gt;uid = estrdup("nntp");
       d-&gt;gid = estrdup("nntp");

       g = a-&gt;g;
       d-&gt;atime = d-&gt;mtime = g-&gt;mtime;

       if(a-&gt;n != -1){      /* article directory */
               if(i &gt;= Nfile)
                       return -1;

               d-&gt;name = estrdup(filename[i]);
               d-&gt;mode = 0444;
               d-&gt;qid = (Qid){PATH(g-&gt;num, a-&gt;n), i, 0};
               return 0;
       }

       /* hierarchy directory: child groups */
       if(i &lt; g-&gt;nkid){
               d-&gt;name = estrdup(g-&gt;kid[i]-&gt;name);
               d-&gt;mode = DMDIR|0555;
               d-&gt;qid = (Qid){PATH(g-&gt;kid[i]-&gt;num, 0), g-&gt;kid[i]-&gt;hi-1, QTDIR};
               return 0;
       }
       i -= g-&gt;nkid;

       /* group directory: post file */
       if(g-&gt;isgroup &amp;&amp; !readonly &amp;&amp; g-&gt;canpost){
               if(i &lt; 1){
                       d-&gt;name = estrdup("post");
                       d-&gt;mode = 0222;
                       d-&gt;qid = (Qid){PATH(g-&gt;num, 0), 0, 0};
                       return 0;
               }
               i--;
       }

       /* group directory: child articles */
       ndir = g-&gt;hi - g-&gt;lo;
       if(i &lt; ndir){
               sprint(buf, "%d", g-&gt;lo+i);
               d-&gt;name = estrdup(buf);
               d-&gt;mode = DMDIR|0555;
               d-&gt;qid = (Qid){PATH(g-&gt;num, i+1), 0, QTDIR};
               return 0;
       }

       return -1;
}

static void
fsstat(Req *r)
{
       Aux *a;

       a = r-&gt;fid-&gt;aux;
       if(r-&gt;fid-&gt;qid.path == 0 &amp;&amp; (r-&gt;fid-&gt;qid.type &amp; QTDIR))
               nntprefreshall(net);
       else if(a-&gt;g-&gt;isgroup)
               nntprefresh(net, a-&gt;g);
       fillstat(&amp;r-&gt;d, a);
       respond(r, nil);
}

static void
fsread(Req *r)
{
       int offset, n;
       Aux *a;
       char *p, *ep;
       Dir d;

       a = r-&gt;fid-&gt;aux;
       if(a-&gt;s){
               readstr(r, a-&gt;s);
               respond(r, nil);
               return;
       }

       if(r-&gt;ifcall.offset == 0)
               offset = 0;
       else
               offset = a-&gt;offset;

       p = r-&gt;ofcall.data;
       ep = r-&gt;ofcall.data+r-&gt;ifcall.count;
       for(; p+2 &lt; ep; p += n){
               if(dirfillstat(&amp;d, a, offset) &lt; 0)
                       break;
               n=convD2M(&amp;d, (uchar*)p, ep-p);
               free(d.name);
               free(d.uid);
               free(d.gid);
               free(d.muid);
               if(n &lt;= BIT16SZ)
                       break;
               offset++;
       }
       a-&gt;offset = offset;
       r-&gt;ofcall.count = p - r-&gt;ofcall.data;
       respond(r, nil);
}

static void
fswrite(Req *r)
{
       Aux *a;
       long count;
       vlong offset;

       a = r-&gt;fid-&gt;aux;

       if(r-&gt;ifcall.count == 0){    /* commit */
               respond(r, nntppost(net, a-&gt;s));
               free(a-&gt;s);
               a-&gt;ns = 0;
               a-&gt;s = nil;
               return;
       }

       count = r-&gt;ifcall.count;
       offset = r-&gt;ifcall.offset;
       if(a-&gt;ns &lt; count+offset+1){
               a-&gt;s = erealloc(a-&gt;s, count+offset+1);
               a-&gt;ns = count+offset;
               a-&gt;s[a-&gt;ns] = '\0';
       }
       memmove(a-&gt;s+offset, r-&gt;ifcall.data, count);
       r-&gt;ofcall.count = count;
       respond(r, nil);
}

static void
fsdestroyfid(Fid *fid)
{
       Aux *a;

       a = fid-&gt;aux;
       if(a==nil)
               return;

       if(a-&gt;ispost &amp;&amp; a-&gt;s)
               nntppost(net, a-&gt;s);

       free(a-&gt;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-&gt;name);
       for(i=0; i&lt;g-&gt;nkid; i++)
               dumpgroups(g-&gt;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(&amp;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 &gt; 1)
               usage();
       if(argc==0)
               where = "$nntp";
       else
               where = argv[0];

       now = time(0);

       net = &amp;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-&gt;user;
               n.pass = up-&gt;passwd;
       }

       n.addr = netmkaddr(where, "tcp", "nntp");

       root = emalloc(sizeof *root);
       root-&gt;name = estrdup("");
       root-&gt;parent = root;

       n.fd = -1;
       if(nntpconnect(&amp;n) &lt; 0)
               sysfatal("nntpconnect: %s", n.response);

       x=netdebug;
       netdebug=0;
       nntprefreshall(&amp;n);
       netdebug=x;
//      dumpgroups(root, 0);

       postmountsrv(&amp;nntpsrv, service, mtpt, MREPL);
       exits(nil);
}

<!-- BEGIN TAIL -->
</pre>
</td></tr></table>
</td></tr></table>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="line-height: 1.2em; margin-left: 1.00in; text-indent: 0.00in; margin-right: 1.00in; margin-top: 0; margin-bottom: 0; text-align: center;">
<span style="font-size: 10pt"></span></p>
<p style="margin-top: 0; margin-bottom: 0.50in"></p>
<p style="margin-top: 0; margin-bottom: 0.33in"></p>
<center><table border="0"><tr>
<td valign="middle"><a href="http://www.alcatel-lucent.com/"><img border="0" src="/plan9/img/logo_ft.gif" alt="Bell Labs" />
</a></td>
<td valign="middle"><a href="http://www.opensource.org"><img border="0" alt="OSI certified" src="/plan9/img/osi-certified-60x50.gif" />
</a></td>
<td><img style="padding-right: 45px;" alt="Powered by Plan 9" src="/plan9/img/power36.gif" />
</td>
</tr></table></center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center>
<span style="font-size: 10pt">(<a href="/plan9/">Return to Plan 9 Home Page</a>)</span>
</center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center><font size=-1>
<span style="font-size: 10pt"><a href="http://www.lucent.com/copyright.html">Copyright</a></span>
<span style="font-size: 10pt">© 2009 Alcatel-Lucent.</span>
<span style="font-size: 10pt">All Rights Reserved.</span>
<br />
<span style="font-size: 10pt">Comments to</span>
<span style="font-size: 10pt"><a href="mailto:[email protected]">[email protected]</a>.</span>
</font></center>
</body>
</html>