1 /* 2 * Network news transport protocol (NNTP) file server. 3 * 4 * Unfortunately, the file system differs from that expected 5 * by Charles Forsyth's rin news reader. This is partially out 6 * of my own laziness, but it makes the bookkeeping here 7 * a lot easier. 8 */ 9 10 #include <u.h> 11 #include <libc.h> 12 #include <bio.h> 13 #include <auth.h> 14 #include <fcall.h> 15 #include <thread.h> 16 #include <9p.h> 17 18 typedef struct Netbuf Netbuf; 19 typedef struct Group Group; 20 21 struct Netbuf { 22 Biobuf br; 23 Biobuf bw; 24 int lineno; 25 int fd; 26 int code; /* last response code */ 27 int auth; /* Authorization required? */ 28 char response[128]; /* last response */ 29 Group *currentgroup; 30 char *addr; 31 char *user; 32 char *pass; 33 ulong extended; /* supported extensions */ 34 }; 35 36 struct Group { 37 char *name; 38 Group *parent; 39 Group **kid; 40 int num; 41 int nkid; 42 int lo, hi; 43 int canpost; 44 int isgroup; /* might just be piece of hierarchy */ 45 ulong mtime; 46 ulong atime; 47 }; 48 49 /* 50 * First eight fields are, in order: 51 * article number, subject, author, date, message-ID, 52 * references, byte count, line count 53 * We don't support OVERVIEW.FMT; when I see a server with more 54 * interesting fields, I'll implement support then. In the meantime, 55 * the standard defines the first eight fields. 56 */ 57 58 /* Extensions */ 59 enum { 60 Nxover = (1<<0), 61 Nxhdr = (1<<1), 62 Nxpat = (1<<2), 63 Nxlistgp = (1<<3), 64 }; 65 66 Group *root; 67 Netbuf *net; 68 ulong now; 69 int netdebug; 70 int readonly; 71 72 void* 73 erealloc(void *v, ulong n) 74 { 75 v = realloc(v, n); 76 if(v == nil) 77 sysfatal("out of memory reallocating %lud", n); 78 setmalloctag(v, getcallerpc(&v)); 79 return v; 80 } 81 82 void* 83 emalloc(ulong n) 84 { 85 void *v; 86 87 v = malloc(n); 88 if(v == nil) 89 sysfatal("out of memory allocating %lud", n); 90 memset(v, 0, n); 91 setmalloctag(v, getcallerpc(&n)); 92 return v; 93 } 94 95 char* 96 estrdup(char *s) 97 { 98 int l; 99 char *t; 100 101 if (s == nil) 102 return nil; 103 l = strlen(s)+1; 104 t = emalloc(l); 105 memcpy(t, s, l); 106 setmalloctag(t, getcallerpc(&s)); 107 return t; 108 } 109 110 char* 111 estrdupn(char *s, int n) 112 { 113 int l; 114 char *t; 115 116 l = strlen(s); 117 if(l > n) 118 l = n; 119 t = emalloc(l+1); 120 memmove(t, s, l); 121 t[l] = '\0'; 122 setmalloctag(t, getcallerpc(&s)); 123 return t; 124 } 125 126 char* 127 Nrdline(Netbuf *n) 128 { 129 char *p; 130 int l; 131 132 n->lineno++; 133 Bflush(&n->bw); 134 if((p = Brdline(&n->br, '\n')) == nil){ 135 werrstr("nntp eof"); 136 return nil; 137 } 138 p[l=Blinelen(&n->br)-1] = '\0'; 139 if(l > 0 && p[l-1] == '\r') 140 p[l-1] = '\0'; 141 if(netdebug) 142 fprint(2, "-> %s\n", p); 143 return p; 144 } 145 146 int 147 nntpresponse(Netbuf *n, int e, char *cmd) 148 { 149 int r; 150 char *p; 151 152 for(;;){ 153 p = Nrdline(n); 154 if(p==nil){ 155 strcpy(n->response, "early nntp eof"); 156 return -1; 157 } 158 r = atoi(p); 159 if(r/100 == 1){ /* BUG? */ 160 fprint(2, "%s\n", p); 161 continue; 162 } 163 break; 164 } 165 166 strecpy(n->response, n->response+sizeof(n->response), p); 167 168 if((r=atoi(p)) == 0){ 169 close(n->fd); 170 n->fd = -1; 171 fprint(2, "bad nntp response: %s\n", p); 172 werrstr("bad nntp response"); 173 return -1; 174 } 175 176 n->code = r; 177 if(0 < e && e<10 && r/100 != e){ 178 fprint(2, "%s: expected %dxx: got %s\n", cmd, e, n->response); 179 return -1; 180 } 181 if(10 <= e && e<100 && r/10 != e){ 182 fprint(2, "%s: expected %dx: got %s\n", cmd, e, n->response); 183 return -1; 184 } 185 if(100 <= e && r != e){ 186 fprint(2, "%s: expected %d: got %s\n", cmd, e, n->response); 187 return -1; 188 } 189 return r; 190 } 191 192 int nntpauth(Netbuf*); 193 int nntpxcmdprobe(Netbuf*); 194 int nntpcurrentgroup(Netbuf*, Group*); 195 196 /* XXX: bug OVER/XOVER et al. */ 197 static struct { 198 ulong n; 199 char *s; 200 } extensions [] = { 201 { Nxover, "OVER" }, 202 { Nxhdr, "HDR" }, 203 { Nxpat, "PAT" }, 204 { Nxlistgp, "LISTGROUP" }, 205 { 0, nil } 206 }; 207 208 static int indial; 209 210 int 211 nntpconnect(Netbuf *n) 212 { 213 n->currentgroup = nil; 214 close(n->fd); 215 if((n->fd = dial(n->addr, nil, nil, nil)) < 0){ 216 snprint(n->response, sizeof n->response, "dial: %r"); 217 return -1; 218 } 219 Binit(&n->br, n->fd, OREAD); 220 Binit(&n->bw, n->fd, OWRITE); 221 if(nntpresponse(n, 20, "greeting") < 0) 222 return -1; 223 readonly = (n->code == 201); 224 225 indial = 1; 226 if(n->auth != 0) 227 nntpauth(n); 228 // nntpxcmdprobe(n); 229 indial = 0; 230 return 0; 231 } 232 233 int 234 nntpcmd(Netbuf *n, char *cmd, int e) 235 { 236 int tried; 237 238 tried = 0; 239 for(;;){ 240 if(netdebug) 241 fprint(2, "<- %s\n", cmd); 242 Bprint(&n->bw, "%s\r\n", cmd); 243 if(nntpresponse(n, e, cmd)>=0 && (e < 0 || n->code/100 != 5)) 244 return 0; 245 246 /* redial */ 247 if(indial || tried++ || nntpconnect(n) < 0) 248 return -1; 249 } 250 251 return -1; /* shut up 8c */ 252 } 253 254 int 255 nntpauth(Netbuf *n) 256 { 257 char cmd[256]; 258 259 snprint(cmd, sizeof cmd, "AUTHINFO USER %s", n->user); 260 if (nntpcmd(n, cmd, -1) < 0 || n->code != 381) { 261 fprint(2, "Authentication failed: %s\n", n->response); 262 return -1; 263 } 264 265 snprint(cmd, sizeof cmd, "AUTHINFO PASS %s", n->pass); 266 if (nntpcmd(n, cmd, -1) < 0 || n->code != 281) { 267 fprint(2, "Authentication failed: %s\n", n->response); 268 return -1; 269 } 270 271 return 0; 272 } 273 274 int 275 nntpxcmdprobe(Netbuf *n) 276 { 277 int i; 278 char *p; 279 280 n->extended = 0; 281 if (nntpcmd(n, "LIST EXTENSIONS", 0) < 0 || n->code != 202) 282 return 0; 283 284 while((p = Nrdline(n)) != nil) { 285 if (strcmp(p, ".") == 0) 286 break; 287 288 for(i=0; extensions[i].s != nil; i++) 289 if (cistrcmp(extensions[i].s, p) == 0) { 290 n->extended |= extensions[i].n; 291 break; 292 } 293 } 294 return 0; 295 } 296 297 /* XXX: searching, lazy evaluation */ 298 static int 299 overcmp(void *v1, void *v2) 300 { 301 int a, b; 302 303 a = atoi(*(char**)v1); 304 b = atoi(*(char**)v2); 305 306 if(a < b) 307 return -1; 308 else if(a > b) 309 return 1; 310 return 0; 311 } 312 313 enum { 314 XoverChunk = 100, 315 }; 316 317 char *xover[XoverChunk]; 318 int xoverlo; 319 int xoverhi; 320 int xovercount; 321 Group *xovergroup; 322 323 char* 324 nntpover(Netbuf *n, Group *g, int m) 325 { 326 int i, lo, hi, mid, msg; 327 char *p; 328 char cmd[64]; 329 330 if (g->isgroup == 0) /* BUG: should check extension capabilities */ 331 return nil; 332 333 if(g != xovergroup || m < xoverlo || m >= xoverhi){ 334 lo = (m/XoverChunk)*XoverChunk; 335 hi = lo+XoverChunk; 336 337 if(lo < g->lo) 338 lo = g->lo; 339 else if (lo > g->hi) 340 lo = g->hi; 341 if(hi < lo || hi > g->hi) 342 hi = g->hi; 343 344 if(nntpcurrentgroup(n, g) < 0) 345 return nil; 346 347 if(lo == hi) 348 snprint(cmd, sizeof cmd, "XOVER %d", hi); 349 else 350 snprint(cmd, sizeof cmd, "XOVER %d-%d", lo, hi-1); 351 if(nntpcmd(n, cmd, 224) < 0) 352 return nil; 353 354 for(i=0; (p = Nrdline(n)) != nil; i++) { 355 if(strcmp(p, ".") == 0) 356 break; 357 if(i >= XoverChunk) 358 sysfatal("news server doesn't play by the rules"); 359 free(xover[i]); 360 xover[i] = emalloc(strlen(p)+2); 361 strcpy(xover[i], p); 362 strcat(xover[i], "\n"); 363 } 364 qsort(xover, i, sizeof(xover[0]), overcmp); 365 366 xovercount = i; 367 368 xovergroup = g; 369 xoverlo = lo; 370 xoverhi = hi; 371 } 372 373 lo = 0; 374 hi = xovercount; 375 /* search for message */ 376 while(lo < hi){ 377 mid = (lo+hi)/2; 378 msg = atoi(xover[mid]); 379 if(m == msg) 380 return xover[mid]; 381 else if(m < msg) 382 hi = mid; 383 else 384 lo = mid+1; 385 } 386 return nil; 387 } 388 389 /* 390 * Return the new Group structure for the group name. 391 * Destroys name. 392 */ 393 static int printgroup(char*,Group*); 394 Group* 395 findgroup(Group *g, char *name, int mk) 396 { 397 int lo, hi, m; 398 char *p, *q; 399 static int ngroup; 400 401 for(p=name; *p; p=q){ 402 if(q = strchr(p, '.')) 403 *q++ = '\0'; 404 else 405 q = p+strlen(p); 406 407 lo = 0; 408 hi = g->nkid; 409 while(hi-lo > 1){ 410 m = (lo+hi)/2; 411 if(strcmp(p, g->kid[m]->name) < 0) 412 hi = m; 413 else 414 lo = m; 415 } 416 assert(lo==hi || lo==hi-1); 417 if(lo==hi || strcmp(p, g->kid[lo]->name) != 0){ 418 if(mk==0) 419 return nil; 420 if(g->nkid%16 == 0) 421 g->kid = erealloc(g->kid, (g->nkid+16)*sizeof(g->kid[0])); 422 423 /* 424 * if we're down to a single place 'twixt lo and hi, the insertion might need 425 * to go at lo or at hi. strcmp to find out. the list needs to stay sorted. 426 */ 427 if(lo==hi-1 && strcmp(p, g->kid[lo]->name) < 0) 428 hi = lo; 429 430 if(hi < g->nkid) 431 memmove(g->kid+hi+1, g->kid+hi, sizeof(g->kid[0])*(g->nkid-hi)); 432 g->nkid++; 433 g->kid[hi] = emalloc(sizeof(*g)); 434 g->kid[hi]->parent = g; 435 g = g->kid[hi]; 436 g->name = estrdup(p); 437 g->num = ++ngroup; 438 g->mtime = time(0); 439 }else 440 g = g->kid[lo]; 441 } 442 if(mk) 443 g->isgroup = 1; 444 return g; 445 } 446 447 static int 448 printgroup(char *s, Group *g) 449 { 450 if(g->parent == g) 451 return 0; 452 453 if(printgroup(s, g->parent)) 454 strcat(s, "."); 455 strcat(s, g->name); 456 return 1; 457 } 458 459 static char* 460 Nreaddata(Netbuf *n) 461 { 462 char *p, *q; 463 int l; 464 465 p = nil; 466 l = 0; 467 for(;;){ 468 q = Nrdline(n); 469 if(q==nil){ 470 free(p); 471 return nil; 472 } 473 if(strcmp(q, ".")==0) 474 return p; 475 if(q[0]=='.') 476 q++; 477 p = erealloc(p, l+strlen(q)+1+1); 478 strcpy(p+l, q); 479 strcat(p+l, "\n"); 480 l += strlen(p+l); 481 } 482 return nil; /* shut up 8c */ 483 } 484 485 /* 486 * Return the output of a HEAD, BODY, or ARTICLE command. 487 */ 488 char* 489 nntpget(Netbuf *n, Group *g, int msg, char *retr) 490 { 491 char *s; 492 char cmd[1024]; 493 494 if(g->isgroup == 0){ 495 werrstr("not a group"); 496 return nil; 497 } 498 499 if(strcmp(retr, "XOVER") == 0){ 500 s = nntpover(n, g, msg); 501 if(s == nil) 502 s = ""; 503 return estrdup(s); 504 } 505 506 if(nntpcurrentgroup(n, g) < 0) 507 return nil; 508 sprint(cmd, "%s %d", retr, msg); 509 nntpcmd(n, cmd, 0); 510 if(n->code/10 != 22) 511 return nil; 512 513 return Nreaddata(n); 514 } 515 516 int 517 nntpcurrentgroup(Netbuf *n, Group *g) 518 { 519 char cmd[1024]; 520 521 if(n->currentgroup != g){ 522 strcpy(cmd, "GROUP "); 523 printgroup(cmd, g); 524 if(nntpcmd(n, cmd, 21) < 0) 525 return -1; 526 n->currentgroup = g; 527 } 528 return 0; 529 } 530 531 void 532 nntprefreshall(Netbuf *n) 533 { 534 char *f[10], *p; 535 int hi, lo, nf; 536 Group *g; 537 538 if(nntpcmd(n, "LIST", 21) < 0) 539 return; 540 541 while(p = Nrdline(n)){ 542 if(strcmp(p, ".")==0) 543 break; 544 545 nf = getfields(p, f, nelem(f), 1, "\t\r\n "); 546 if(nf != 4){ 547 int i; 548 for(i=0; i<nf; i++) 549 fprint(2, "%s%s", i?" ":"", f[i]); 550 fprint(2, "\n"); 551 fprint(2, "syntax error in group list, line %d", n->lineno); 552 return; 553 } 554 g = findgroup(root, f[0], 1); 555 hi = strtol(f[1], 0, 10)+1; 556 lo = strtol(f[2], 0, 10); 557 if(g->hi != hi){ 558 g->hi = hi; 559 if(g->lo==0) 560 g->lo = lo; 561 g->canpost = f[3][0] == 'y'; 562 g->mtime = time(0); 563 } 564 } 565 } 566 567 void 568 nntprefresh(Netbuf *n, Group *g) 569 { 570 char cmd[1024]; 571 char *f[5]; 572 int lo, hi; 573 574 if(g->isgroup==0) 575 return; 576 577 if(time(0) - g->atime < 30) 578 return; 579 580 strcpy(cmd, "GROUP "); 581 printgroup(cmd, g); 582 if(nntpcmd(n, cmd, 21) < 0){ 583 n->currentgroup = nil; 584 return; 585 } 586 n->currentgroup = g; 587 588 if(tokenize(n->response, f, nelem(f)) < 4){ 589 fprint(2, "error reading GROUP response"); 590 return; 591 } 592 593 /* backwards from LIST! */ 594 hi = strtol(f[3], 0, 10)+1; 595 lo = strtol(f[2], 0, 10); 596 if(g->hi != hi){ 597 g->mtime = time(0); 598 if(g->lo==0) 599 g->lo = lo; 600 g->hi = hi; 601 } 602 g->atime = time(0); 603 } 604 605 char* 606 nntppost(Netbuf *n, char *msg) 607 { 608 char *p, *q; 609 610 if(nntpcmd(n, "POST", 34) < 0) 611 return n->response; 612 613 for(p=msg; *p; p=q){ 614 if(q = strchr(p, '\n')) 615 *q++ = '\0'; 616 else 617 q = p+strlen(p); 618 619 if(p[0]=='.') 620 Bputc(&n->bw, '.'); 621 Bwrite(&n->bw, p, strlen(p)); 622 Bputc(&n->bw, '\r'); 623 Bputc(&n->bw, '\n'); 624 } 625 Bprint(&n->bw, ".\r\n"); 626 627 if(nntpresponse(n, 0, nil) < 0) 628 return n->response; 629 630 if(n->code/100 != 2) 631 return n->response; 632 return nil; 633 } 634 635 /* 636 * Because an expanded QID space makes thngs much easier, 637 * we sleazily use the version part of the QID as more path bits. 638 * Since we make sure not to mount ourselves cached, this 639 * doesn't break anything (unless you want to bind on top of 640 * things in this file system). In the next version of 9P, we'll 641 * have more QID bits to play with. 642 * 643 * The newsgroup is encoded in the top 15 bits 644 * of the path. The message number is the bottom 17 bits. 645 * The file within the message directory is in the version [sic]. 646 */ 647 648 enum { /* file qids */ 649 Qhead, 650 Qbody, 651 Qarticle, 652 Qxover, 653 Nfile, 654 }; 655 char *filename[] = { 656 "header", 657 "body", 658 "article", 659 "xover", 660 }; 661 char *nntpname[] = { 662 "HEAD", 663 "BODY", 664 "ARTICLE", 665 "XOVER", 666 }; 667 668 #define GROUP(p) (((p)>>17)&0x3FFF) 669 #define MESSAGE(p) ((p)&0x1FFFF) 670 #define FILE(v) ((v)&0x3) 671 672 #define PATH(g,m) ((((g)&0x3FFF)<<17)|((m)&0x1FFFF)) 673 #define POST(g) PATH(0,g,0) 674 #define VERS(f) ((f)&0x3) 675 676 typedef struct Aux Aux; 677 struct Aux { 678 Group *g; 679 int n; 680 int ispost; 681 int file; 682 char *s; 683 int ns; 684 int offset; 685 }; 686 687 static void 688 fsattach(Req *r) 689 { 690 Aux *a; 691 char *spec; 692 693 spec = r->ifcall.aname; 694 if(spec && spec[0]){ 695 respond(r, "invalid attach specifier"); 696 return; 697 } 698 699 a = emalloc(sizeof *a); 700 a->g = root; 701 a->n = -1; 702 r->fid->aux = a; 703 704 r->ofcall.qid = (Qid){0, 0, QTDIR}; 705 r->fid->qid = r->ofcall.qid; 706 respond(r, nil); 707 } 708 709 static char* 710 fsclone(Fid *ofid, Fid *fid) 711 { 712 Aux *a; 713 714 a = emalloc(sizeof(*a)); 715 *a = *(Aux*)ofid->aux; 716 fid->aux = a; 717 return nil; 718 } 719 720 static char* 721 fswalk1(Fid *fid, char *name, Qid *qid) 722 { 723 char *p; 724 int i, isdotdot, n; 725 Aux *a; 726 Group *ng; 727 728 isdotdot = strcmp(name, "..")==0; 729 730 a = fid->aux; 731 if(a->s) /* file */ 732 return "protocol botch"; 733 if(a->n != -1){ 734 if(isdotdot){ 735 *qid = (Qid){PATH(a->g->num, 0), 0, QTDIR}; 736 fid->qid = *qid; 737 a->n = -1; 738 return nil; 739 } 740 for(i=0; i<Nfile; i++){ 741 if(strcmp(name, filename[i])==0){ 742 if(a->s = nntpget(net, a->g, a->n, nntpname[i])){ 743 *qid = (Qid){PATH(a->g->num, a->n), Qbody, 0}; 744 fid->qid = *qid; 745 a->file = i; 746 return nil; 747 }else 748 return "file does not exist"; 749 } 750 } 751 return "file does not exist"; 752 } 753 754 if(isdotdot){ 755 a->g = a->g->parent; 756 *qid = (Qid){PATH(a->g->num, 0), 0, QTDIR}; 757 fid->qid = *qid; 758 return nil; 759 } 760 761 if(a->g->isgroup && !readonly && a->g->canpost 762 && strcmp(name, "post")==0){ 763 a->ispost = 1; 764 *qid = (Qid){PATH(a->g->num, 0), 0, 0}; 765 fid->qid = *qid; 766 return nil; 767 } 768 769 if(ng = findgroup(a->g, name, 0)){ 770 a->g = ng; 771 *qid = (Qid){PATH(a->g->num, 0), 0, QTDIR}; 772 fid->qid = *qid; 773 return nil; 774 } 775 776 n = strtoul(name, &p, 0); 777 if('0'<=name[0] && name[0]<='9' && *p=='\0' && a->g->lo<=n && n<a->g->hi){ 778 a->n = n; 779 *qid = (Qid){PATH(a->g->num, n+1-a->g->lo), 0, QTDIR}; 780 fid->qid = *qid; 781 return nil; 782 } 783 784 return "file does not exist"; 785 } 786 787 static void 788 fsopen(Req *r) 789 { 790 Aux *a; 791 792 a = r->fid->aux; 793 if((a->ispost && (r->ifcall.mode&~OTRUNC) != OWRITE) 794 || (!a->ispost && r->ifcall.mode != OREAD)) 795 respond(r, "permission denied"); 796 else 797 respond(r, nil); 798 } 799 800 static void 801 fillstat(Dir *d, Aux *a) 802 { 803 char buf[32]; 804 Group *g; 805 806 memset(d, 0, sizeof *d); 807 d->uid = estrdup("nntp"); 808 d->gid = estrdup("nntp"); 809 g = a->g; 810 d->atime = d->mtime = g->mtime; 811 812 if(a->ispost){ 813 d->name = estrdup("post"); 814 d->mode = 0222; 815 d->qid = (Qid){PATH(g->num, 0), 0, 0}; 816 d->length = a->ns; 817 return; 818 } 819 820 if(a->s){ /* article file */ 821 d->name = estrdup(filename[a->file]); 822 d->mode = 0444; 823 d->qid = (Qid){PATH(g->num, a->n+1-g->lo), a->file, 0}; 824 return; 825 } 826 827 if(a->n != -1){ /* article directory */ 828 sprint(buf, "%d", a->n); 829 d->name = estrdup(buf); 830 d->mode = DMDIR|0555; 831 d->qid = (Qid){PATH(g->num, a->n+1-g->lo), 0, QTDIR}; 832 return; 833 } 834 835 /* group directory */ 836 if(g->name[0]) 837 d->name = estrdup(g->name); 838 else 839 d->name = estrdup("/"); 840 d->mode = DMDIR|0555; 841 d->qid = (Qid){PATH(g->num, 0), g->hi-1, QTDIR}; 842 } 843 844 static int 845 dirfillstat(Dir *d, Aux *a, int i) 846 { 847 int ndir; 848 Group *g; 849 char buf[32]; 850 851 memset(d, 0, sizeof *d); 852 d->uid = estrdup("nntp"); 853 d->gid = estrdup("nntp"); 854 855 g = a->g; 856 d->atime = d->mtime = g->mtime; 857 858 if(a->n != -1){ /* article directory */ 859 if(i >= Nfile) 860 return -1; 861 862 d->name = estrdup(filename[i]); 863 d->mode = 0444; 864 d->qid = (Qid){PATH(g->num, a->n), i, 0}; 865 return 0; 866 } 867 868 /* hierarchy directory: child groups */ 869 if(i < g->nkid){ 870 d->name = estrdup(g->kid[i]->name); 871 d->mode = DMDIR|0555; 872 d->qid = (Qid){PATH(g->kid[i]->num, 0), g->kid[i]->hi-1, QTDIR}; 873 return 0; 874 } 875 i -= g->nkid; 876 877 /* group directory: post file */ 878 if(g->isgroup && !readonly && g->canpost){ 879 if(i < 1){ 880 d->name = estrdup("post"); 881 d->mode = 0222; 882 d->qid = (Qid){PATH(g->num, 0), 0, 0}; 883 return 0; 884 } 885 i--; 886 } 887 888 /* group directory: child articles */ 889 ndir = g->hi - g->lo; 890 if(i < ndir){ 891 sprint(buf, "%d", g->lo+i); 892 d->name = estrdup(buf); 893 d->mode = DMDIR|0555; 894 d->qid = (Qid){PATH(g->num, i+1), 0, QTDIR}; 895 return 0; 896 } 897 898 return -1; 899 } 900 901 static void 902 fsstat(Req *r) 903 { 904 Aux *a; 905 906 a = r->fid->aux; 907 if(r->fid->qid.path == 0 && (r->fid->qid.type & QTDIR)) 908 nntprefreshall(net); 909 else if(a->g->isgroup) 910 nntprefresh(net, a->g); 911 fillstat(&r->d, a); 912 respond(r, nil); 913 } 914 915 static void 916 fsread(Req *r) 917 { 918 int offset, n; 919 Aux *a; 920 char *p, *ep; 921 Dir d; 922 923 a = r->fid->aux; 924 if(a->s){ 925 readstr(r, a->s); 926 respond(r, nil); 927 return; 928 } 929 930 if(r->ifcall.offset == 0) 931 offset = 0; 932 else 933 offset = a->offset; 934 935 p = r->ofcall.data; 936 ep = r->ofcall.data+r->ifcall.count; 937 for(; p+2 < ep; p += n){ 938 if(dirfillstat(&d, a, offset) < 0) 939 break; 940 n=convD2M(&d, (uchar*)p, ep-p); 941 free(d.name); 942 free(d.uid); 943 free(d.gid); 944 free(d.muid); 945 if(n <= BIT16SZ) 946 break; 947 offset++; 948 } 949 a->offset = offset; 950 r->ofcall.count = p - r->ofcall.data; 951 respond(r, nil); 952 } 953 954 static void 955 fswrite(Req *r) 956 { 957 Aux *a; 958 long count; 959 vlong offset; 960 961 a = r->fid->aux; 962 963 if(r->ifcall.count == 0){ /* commit */ 964 respond(r, nntppost(net, a->s)); 965 free(a->s); 966 a->ns = 0; 967 a->s = nil; 968 return; 969 } 970 971 count = r->ifcall.count; 972 offset = r->ifcall.offset; 973 if(a->ns < count+offset+1){ 974 a->s = erealloc(a->s, count+offset+1); 975 a->ns = count+offset; 976 a->s[a->ns] = '\0'; 977 } 978 memmove(a->s+offset, r->ifcall.data, count); 979 r->ofcall.count = count; 980 respond(r, nil); 981 } 982 983 static void 984 fsdestroyfid(Fid *fid) 985 { 986 Aux *a; 987 988 a = fid->aux; 989 if(a==nil) 990 return; 991 992 if(a->ispost && a->s) 993 nntppost(net, a->s); 994 995 free(a->s); 996 free(a); 997 } 998 999 Srv nntpsrv = { 1000 .destroyfid= fsdestroyfid, 1001 .attach= fsattach, 1002 .clone= fsclone, 1003 .walk1= fswalk1, 1004 .open= fsopen, 1005 .read= fsread, 1006 .write= fswrite, 1007 .stat= fsstat, 1008 }; 1009 1010 void 1011 usage(void) 1012 { 1013 fprint(2, "usage: nntpsrv [-a] [-s service] [-m mtpt] [nntp.server]\n"); 1014 exits("usage"); 1015 } 1016 1017 void 1018 dumpgroups(Group *g, int ind) 1019 { 1020 int i; 1021 1022 print("%*s%s\n", ind*4, "", g->name); 1023 for(i=0; i<g->nkid; i++) 1024 dumpgroups(g->kid[i], ind+1); 1025 } 1026 1027 void 1028 main(int argc, char **argv) 1029 { 1030 int auth, x; 1031 char *mtpt, *service, *where, *user; 1032 Netbuf n; 1033 UserPasswd *up; 1034 1035 mtpt = "/mnt/news"; 1036 service = nil; 1037 memset(&n, 0, sizeof n); 1038 user = nil; 1039 auth = 0; 1040 ARGBEGIN{ 1041 case 'D': 1042 chatty9p++; 1043 break; 1044 case 'N': 1045 netdebug = 1; 1046 break; 1047 case 'a': 1048 auth = 1; 1049 break; 1050 case 'u': 1051 user = EARGF(usage()); 1052 break; 1053 case 's': 1054 service = EARGF(usage()); 1055 break; 1056 case 'm': 1057 mtpt = EARGF(usage()); 1058 break; 1059 default: 1060 usage(); 1061 }ARGEND 1062 1063 if(argc > 1) 1064 usage(); 1065 if(argc==0) 1066 where = "$nntp"; 1067 else 1068 where = argv[0]; 1069 1070 now = time(0); 1071 1072 net = &n; 1073 if(auth) { 1074 n.auth = 1; 1075 if(user) 1076 up = auth_getuserpasswd(auth_getkey, "proto=pass service=nntp server=%q user=%q", where, user); 1077 else 1078 up = auth_getuserpasswd(auth_getkey, "proto=pass service=nntp server=%q", where); 1079 if(up == nil) 1080 sysfatal("no password: %r"); 1081 1082 n.user = up->user; 1083 n.pass = up->passwd; 1084 } 1085 1086 n.addr = netmkaddr(where, "tcp", "nntp"); 1087 1088 root = emalloc(sizeof *root); 1089 root->name = estrdup(""); 1090 root->parent = root; 1091 1092 n.fd = -1; 1093 if(nntpconnect(&n) < 0) 1094 sysfatal("nntpconnect: %s", n.response); 1095 1096 x=netdebug; 1097 netdebug=0; 1098 nntprefreshall(&n); 1099 netdebug=x; 1100 // dumpgroups(root, 0); 1101 1102 postmountsrv(&nntpsrv, service, mtpt, MREPL); 1103 exits(nil); 1104 } 1105 1106