1103 lines
18 KiB
C
1103 lines
18 KiB
C
|
/*
|
||
|
* 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);
|
||
|
}
|
||
|
|