/* * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ /* * 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;} intnntpresponse(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; intnntpconnect(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 %s: %r", n->addr); 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;} intnntpcmd(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; }} intnntpauth(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;} intnntpxcmdprobe(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 intovercmp(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 intprintgroup(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){Lines 1–500 of 1111. Load more
/* * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ /* * 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 %s: %r", n->addr); 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){