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 %s: %r", n->addr); 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 252 int 253 nntpauth(Netbuf *n) 254 { 255 char cmd[256]; 256 257 snprint(cmd, sizeof cmd, "AUTHINFO USER %s", n->user); 258 if (nntpcmd(n, cmd, -1) < 0 || n->code != 381) { 259 fprint(2, "Authentication failed: %s\n", n->response); 260 return -1; 261 } 262 263 snprint(cmd, sizeof cmd, "AUTHINFO PASS %s", n->pass); 264 if (nntpcmd(n, cmd, -1) < 0 || n->code != 281) { 265 fprint(2, "Authentication failed: %s\n", n->response); 266 return -1; 267 } 268 269 return 0; 270 } 271 272 int 273 nntpxcmdprobe(Netbuf *n) 274 { 275 int i; 276 char *p; 277 278 n->extended = 0; 279 if (nntpcmd(n, "LIST EXTENSIONS", 0) < 0 || n->code != 202) 280 return 0; 281 282 while((p = Nrdline(n)) != nil) { 283 if (strcmp(p, ".") == 0) 284 break; 285 286 for(i=0; extensions[i].s != nil; i++) 287 if (cistrcmp(extensions[i].s, p) == 0) { 288 n->extended |= extensions[i].n; 289 break; 290 } 291 } 292 return 0; 293 } 294 295 /* XXX: searching, lazy evaluation */ 296 static int 297 overcmp(void *v1, void *v2) 298 { 299 int a, b; 300 301 a = atoi(*(char**)v1); 302 b = atoi(*(char**)v2); 303 304 if(a < b) 305 return -1; 306 else if(a > b) 307 return 1; 308 return 0; 309 } 310 311 enum { 312 XoverChunk = 100, 313 }; 314 315 char *xover[XoverChunk]; 316 int xoverlo; 317 int xoverhi; 318 int xovercount; 319 Group *xovergroup; 320 321 char* 322 nntpover(Netbuf *n, Group *g, int m) 323 { 324 int i, lo, hi, mid, msg; 325 char *p; 326 char cmd[64]; 327 328 if (g->isgroup == 0) /* BUG: should check extension capabilities */ 329 return nil; 330 331 if(g != xovergroup || m < xoverlo || m >= xoverhi){ 332 lo = (m/XoverChunk)*XoverChunk; 333 hi = lo+XoverChunk; 334 335 if(lo < g->lo) 336 lo = g->lo; 337 else if (lo > g->hi) 338 lo = g->hi; 339 if(hi < lo || hi > g->hi) 340 hi = g->hi; 341 342 if(nntpcurrentgroup(n, g) < 0) 343 return nil; 344 345 if(lo == hi) 346 snprint(cmd, sizeof cmd, "XOVER %d", hi); 347 else 348 snprint(cmd, sizeof cmd, "XOVER %d-%d", lo, hi-1); 349 if(nntpcmd(n, cmd, 224) < 0) 350 return nil; 351 352 for(i=0; (p = Nrdline(n)) != nil; i++) { 353 if(strcmp(p, ".") == 0) 354 break; 355 if(i >= XoverChunk) 356 sysfatal("news server doesn't play by the rules"); 357 free(xover[i]); 358 xover[i] = emalloc(strlen(p)+2); 359 strcpy(xover[i], p); 360 strcat(xover[i], "\n"); 361 } 362 qsort(xover, i, sizeof(xover[0]), overcmp); 363 364 xovercount = i; 365 366 xovergroup = g; 367 xoverlo = lo; 368 xoverhi = hi; 369 } 370 371 lo = 0; 372 hi = xovercount; 373 /* search for message */ 374 while(lo < hi){ 375 mid = (lo+hi)/2; 376 msg = atoi(xover[mid]); 377 if(m == msg) 378 return xover[mid]; 379 else if(m < msg) 380 hi = mid; 381 else 382 lo = mid+1; 383 } 384 return nil; 385 } 386 387 /* 388 * Return the new Group structure for the group name. 389 * Destroys name. 390 */ 391 static int printgroup(char*,Group*); 392 Group* 393 findgroup(Group *g, char *name, int mk) 394 { 395 int lo, hi, m; 396 char *p, *q; 397 static int ngroup; 398 399 for(p=name; *p; p=q){ 400 if(q = strchr(p, '.')) 401 *q++ = '\0'; 402 else 403 q = p+strlen(p); 404 405 lo = 0; 406 hi = g->nkid; 407 while(hi-lo > 1){ 408 m = (lo+hi)/2; 409 if(strcmp(p, g->kid[m]->name) < 0) 410 hi = m; 411 else 412 lo = m; 413 } 414 assert(lo==hi || lo==hi-1); 415 if(lo==hi || strcmp(p, g->kid[lo]->name) != 0){ 416 if(mk==0) 417 return nil; 418 if(g->nkid%16 == 0) 419 g->kid = erealloc(g->kid, (g->nkid+16)*sizeof(g->kid[0])); 420 421 /* 422 * if we're down to a single place 'twixt lo and hi, the insertion might need 423 * to go at lo or at hi. strcmp to find out. the list needs to stay sorted. 424 */ 425 if(lo==hi-1 && strcmp(p, g->kid[lo]->name) < 0) 426 hi = lo; 427 428 if(hi < g->nkid) 429 memmove(g->kid+hi+1, g->kid+hi, sizeof(g->kid[0])*(g->nkid-hi)); 430 g->nkid++; 431 g->kid[hi] = emalloc(sizeof(*g)); 432 g->kid[hi]->parent = g; 433 g = g->kid[hi]; 434 g->name = estrdup(p); 435 g->num = ++ngroup; 436 g->mtime = time(0); 437 }else 438 g = g->kid[lo]; 439 } 440 if(mk) 441 g->isgroup = 1; 442 return g; 443 } 444 445 static int 446 printgroup(char *s, Group *g) 447 { 448 if(g->parent == g) 449 return 0; 450 451 if(printgroup(s, g->parent)) 452 strcat(s, "."); 453 strcat(s, g->name); 454 return 1; 455 } 456 457 static char* 458 Nreaddata(Netbuf *n) 459 { 460 char *p, *q; 461 int l; 462 463 p = nil; 464 l = 0; 465 for(;;){ 466 q = Nrdline(n); 467 if(q==nil){ 468 free(p); 469 return nil; 470 } 471 if(strcmp(q, ".")==0) 472 return p; 473 if(q[0]=='.') 474 q++; 475 p = erealloc(p, l+strlen(q)+1+1); 476 strcpy(p+l, q); 477 strcat(p+l, "\n"); 478 l += strlen(p+l); 479 } 480 } 481 482 /* 483 * Return the output of a HEAD, BODY, or ARTICLE command. 484 */ 485 char* 486 nntpget(Netbuf *n, Group *g, int msg, char *retr) 487 { 488 char *s; 489 char cmd[1024]; 490 491 if(g->isgroup == 0){ 492 werrstr("not a group"); 493 return nil; 494 } 495 496 if(strcmp(retr, "XOVER") == 0){ 497 s = nntpover(n, g, msg); 498 if(s == nil) 499 s = ""; 500 return estrdup(s); 501 } 502 503 if(nntpcurrentgroup(n, g) < 0) 504 return nil; 505 sprint(cmd, "%s %d", retr, msg); 506 nntpcmd(n, cmd, 0); 507 if(n->code/10 != 22) 508 return nil; 509 510 return Nreaddata(n); 511 } 512 513 int 514 nntpcurrentgroup(Netbuf *n, Group *g) 515 { 516 char cmd[1024]; 517 518 if(n->currentgroup != g){ 519 strcpy(cmd, "GROUP "); 520 printgroup(cmd, g); 521 if(nntpcmd(n, cmd, 21) < 0) 522 return -1; 523 n->currentgroup = g; 524 } 525 return 0; 526 } 527 528 void 529 nntprefreshall(Netbuf *n) 530 { 531 char *f[10], *p; 532 int hi, lo, nf; 533 Group *g; 534 535 if(nntpcmd(n, "LIST", 21) < 0) 536 return; 537 538 while(p = Nrdline(n)){ 539 if(strcmp(p, ".")==0) 540 break; 541 542 nf = getfields(p, f, nelem(f), 1, "\t\r\n "); 543 if(nf != 4){ 544 int i; 545 for(i=0; i<nf; i++) 546 fprint(2, "%s%s", i?" ":"", f[i]); 547 fprint(2, "\n"); 548 fprint(2, "syntax error in group list, line %d", n->lineno); 549 return; 550 } 551 g = findgroup(root, f[0], 1); 552 hi = strtol(f[1], 0, 10)+1; 553 lo = strtol(f[2], 0, 10); 554 if(g->hi != hi){ 555 g->hi = hi; 556 if(g->lo==0) 557 g->lo = lo; 558 g->canpost = f[3][0] == 'y'; 559 g->mtime = time(0); 560 } 561 } 562 } 563 564 void 565 nntprefresh(Netbuf *n, Group *g) 566 { 567 char cmd[1024]; 568 char *f[5]; 569 int lo, hi; 570 571 if(g->isgroup==0) 572 return; 573 574 if(time(0) - g->atime < 30) 575 return; 576 577 strcpy(cmd, "GROUP "); 578 printgroup(cmd, g); 579 if(nntpcmd(n, cmd, 21) < 0){ 580 n->currentgroup = nil; 581 return; 582 } 583 n->currentgroup = g; 584 585 if(tokenize(n->response, f, nelem(f)) < 4){ 586 fprint(2, "error reading GROUP response"); 587 return; 588 } 589 590 /* backwards from LIST! */ 591 hi = strtol(f[3], 0, 10)+1; 592 lo = strtol(f[2], 0, 10); 593 if(g->hi != hi){ 594 g->mtime = time(0); 595 if(g->lo==0) 596 g->lo = lo; 597 g->hi = hi; 598 } 599 g->atime = time(0); 600 } 601 602 char* 603 nntppost(Netbuf *n, char *msg) 604 { 605 char *p, *q; 606 607 if(nntpcmd(n, "POST", 34) < 0) 608 return n->response; 609 610 for(p=msg; *p; p=q){ 611 if(q = strchr(p, '\n')) 612 *q++ = '\0'; 613 else 614 q = p+strlen(p); 615 616 if(p[0]=='.') 617 Bputc(&n->bw, '.'); 618 Bwrite(&n->bw, p, strlen(p)); 619 Bputc(&n->bw, '\r'); 620 Bputc(&n->bw, '\n'); 621 } 622 Bprint(&n->bw, ".\r\n"); 623 624 if(nntpresponse(n, 0, nil) < 0) 625 return n->response; 626 627 if(n->code/100 != 2) 628 return n->response; 629 return nil; 630 } 631 632 /* 633 * Because an expanded QID space makes thngs much easier, 634 * we sleazily use the version part of the QID as more path bits. 635 * Since we make sure not to mount ourselves cached, this 636 * doesn't break anything (unless you want to bind on top of 637 * things in this file system). In the next version of 9P, we'll 638 * have more QID bits to play with. 639 * 640 * The newsgroup is encoded in the top 15 bits 641 * of the path. The message number is the bottom 17 bits. 642 * The file within the message directory is in the version [sic]. 643 */ 644 645 enum { /* file qids */ 646 Qhead, 647 Qbody, 648 Qarticle, 649 Qxover, 650 Nfile, 651 }; 652 char *filename[] = { 653 "header", 654 "body", 655 "article", 656 "xover", 657 }; 658 char *nntpname[] = { 659 "HEAD", 660 "BODY", 661 "ARTICLE", 662 "XOVER", 663 }; 664 665 #define GROUP(p) (((p)>>17)&0x3FFF) 666 #define MESSAGE(p) ((p)&0x1FFFF) 667 #define FILE(v) ((v)&0x3) 668 669 #define PATH(g,m) ((((g)&0x3FFF)<<17)|((m)&0x1FFFF)) 670 #define POST(g) PATH(0,g,0) 671 #define VERS(f) ((f)&0x3) 672 673 typedef struct Aux Aux; 674 struct Aux { 675 Group *g; 676 int n; 677 int ispost; 678 int file; 679 char *s; 680 int ns; 681 int offset; 682 }; 683 684 static void 685 fsattach(Req *r) 686 { 687 Aux *a; 688 char *spec; 689 690 spec = r->ifcall.aname; 691 if(spec && spec[0]){ 692 respond(r, "invalid attach specifier"); 693 return; 694 } 695 696 a = emalloc(sizeof *a); 697 a->g = root; 698 a->n = -1; 699 r->fid->aux = a; 700 701 r->ofcall.qid = (Qid){0, 0, QTDIR}; 702 r->fid->qid = r->ofcall.qid; 703 respond(r, nil); 704 } 705 706 static char* 707 fsclone(Fid *ofid, Fid *fid) 708 { 709 Aux *a; 710 711 a = emalloc(sizeof(*a)); 712 *a = *(Aux*)ofid->aux; 713 fid->aux = a; 714 return nil; 715 } 716 717 static char* 718 fswalk1(Fid *fid, char *name, Qid *qid) 719 { 720 char *p; 721 int i, isdotdot, n; 722 Aux *a; 723 Group *ng; 724 725 isdotdot = strcmp(name, "..")==0; 726 727 a = fid->aux; 728 if(a->s) /* file */ 729 return "protocol botch"; 730 if(a->n != -1){ 731 if(isdotdot){ 732 *qid = (Qid){PATH(a->g->num, 0), 0, QTDIR}; 733 fid->qid = *qid; 734 a->n = -1; 735 return nil; 736 } 737 for(i=0; i<Nfile; i++){ 738 if(strcmp(name, filename[i])==0){ 739 if(a->s = nntpget(net, a->g, a->n, nntpname[i])){ 740 *qid = (Qid){PATH(a->g->num, a->n), Qbody, 0}; 741 fid->qid = *qid; 742 a->file = i; 743 return nil; 744 }else 745 return "file does not exist"; 746 } 747 } 748 return "file does not exist"; 749 } 750 751 if(isdotdot){ 752 a->g = a->g->parent; 753 *qid = (Qid){PATH(a->g->num, 0), 0, QTDIR}; 754 fid->qid = *qid; 755 return nil; 756 } 757 758 if(a->g->isgroup && !readonly && a->g->canpost 759 && strcmp(name, "post")==0){ 760 a->ispost = 1; 761 *qid = (Qid){PATH(a->g->num, 0), 0, 0}; 762 fid->qid = *qid; 763 return nil; 764 } 765 766 if(ng = findgroup(a->g, name, 0)){ 767 a->g = ng; 768 *qid = (Qid){PATH(a->g->num, 0), 0, QTDIR}; 769 fid->qid = *qid; 770 return nil; 771 } 772 773 n = strtoul(name, &p, 0); 774 if('0'<=name[0] && name[0]<='9' && *p=='\0' && a->g->lo<=n && n<a->g->hi){ 775 a->n = n; 776 *qid = (Qid){PATH(a->g->num, n+1-a->g->lo), 0, QTDIR}; 777 fid->qid = *qid; 778 return nil; 779 } 780 781 return "file does not exist"; 782 } 783 784 static void 785 fsopen(Req *r) 786 { 787 Aux *a; 788 789 a = r->fid->aux; 790 if((a->ispost && (r->ifcall.mode&~OTRUNC) != OWRITE) 791 || (!a->ispost && r->ifcall.mode != OREAD)) 792 respond(r, "permission denied"); 793 else 794 respond(r, nil); 795 } 796 797 static void 798 fillstat(Dir *d, Aux *a) 799 { 800 char buf[32]; 801 Group *g; 802 803 memset(d, 0, sizeof *d); 804 d->uid = estrdup("nntp"); 805 d->gid = estrdup("nntp"); 806 g = a->g; 807 d->atime = d->mtime = g->mtime; 808 809 if(a->ispost){ 810 d->name = estrdup("post"); 811 d->mode = 0222; 812 d->qid = (Qid){PATH(g->num, 0), 0, 0}; 813 d->length = a->ns; 814 return; 815 } 816 817 if(a->s){ /* article file */ 818 d->name = estrdup(filename[a->file]); 819 d->mode = 0444; 820 d->qid = (Qid){PATH(g->num, a->n+1-g->lo), a->file, 0}; 821 return; 822 } 823 824 if(a->n != -1){ /* article directory */ 825 sprint(buf, "%d", a->n); 826 d->name = estrdup(buf); 827 d->mode = DMDIR|0555; 828 d->qid = (Qid){PATH(g->num, a->n+1-g->lo), 0, QTDIR}; 829 return; 830 } 831 832 /* group directory */ 833 if(g->name[0]) 834 d->name = estrdup(g->name); 835 else 836 d->name = estrdup("/"); 837 d->mode = DMDIR|0555; 838 d->qid = (Qid){PATH(g->num, 0), g->hi-1, QTDIR}; 839 } 840 841 static int 842 dirfillstat(Dir *d, Aux *a, int i) 843 { 844 int ndir; 845 Group *g; 846 char buf[32]; 847 848 memset(d, 0, sizeof *d); 849 d->uid = estrdup("nntp"); 850 d->gid = estrdup("nntp"); 851 852 g = a->g; 853 d->atime = d->mtime = g->mtime; 854 855 if(a->n != -1){ /* article directory */ 856 if(i >= Nfile) 857 return -1; 858 859 d->name = estrdup(filename[i]); 860 d->mode = 0444; 861 d->qid = (Qid){PATH(g->num, a->n), i, 0}; 862 return 0; 863 } 864 865 /* hierarchy directory: child groups */ 866 if(i < g->nkid){ 867 d->name = estrdup(g->kid[i]->name); 868 d->mode = DMDIR|0555; 869 d->qid = (Qid){PATH(g->kid[i]->num, 0), g->kid[i]->hi-1, QTDIR}; 870 return 0; 871 } 872 i -= g->nkid; 873 874 /* group directory: post file */ 875 if(g->isgroup && !readonly && g->canpost){ 876 if(i < 1){ 877 d->name = estrdup("post"); 878 d->mode = 0222; 879 d->qid = (Qid){PATH(g->num, 0), 0, 0}; 880 return 0; 881 } 882 i--; 883 } 884 885 /* group directory: child articles */ 886 ndir = g->hi - g->lo; 887 if(i < ndir){ 888 sprint(buf, "%d", g->lo+i); 889 d->name = estrdup(buf); 890 d->mode = DMDIR|0555; 891 d->qid = (Qid){PATH(g->num, i+1), 0, QTDIR}; 892 return 0; 893 } 894 895 return -1; 896 } 897 898 static void 899 fsstat(Req *r) 900 { 901 Aux *a; 902 903 a = r->fid->aux; 904 if(r->fid->qid.path == 0 && (r->fid->qid.type & QTDIR)) 905 nntprefreshall(net); 906 else if(a->g->isgroup) 907 nntprefresh(net, a->g); 908 fillstat(&r->d, a); 909 respond(r, nil); 910 } 911 912 static void 913 fsread(Req *r) 914 { 915 int offset, n; 916 Aux *a; 917 char *p, *ep; 918 Dir d; 919 920 a = r->fid->aux; 921 if(a->s){ 922 readstr(r, a->s); 923 respond(r, nil); 924 return; 925 } 926 927 if(r->ifcall.offset == 0) 928 offset = 0; 929 else 930 offset = a->offset; 931 932 p = r->ofcall.data; 933 ep = r->ofcall.data+r->ifcall.count; 934 for(; p+2 < ep; p += n){ 935 if(dirfillstat(&d, a, offset) < 0) 936 break; 937 n=convD2M(&d, (uchar*)p, ep-p); 938 free(d.name); 939 free(d.uid); 940 free(d.gid); 941 free(d.muid); 942 if(n <= BIT16SZ) 943 break; 944 offset++; 945 } 946 a->offset = offset; 947 r->ofcall.count = p - r->ofcall.data; 948 respond(r, nil); 949 } 950 951 static void 952 fswrite(Req *r) 953 { 954 Aux *a; 955 long count; 956 vlong offset; 957 958 a = r->fid->aux; 959 960 if(r->ifcall.count == 0){ /* commit */ 961 respond(r, nntppost(net, a->s)); 962 free(a->s); 963 a->ns = 0; 964 a->s = nil; 965 return; 966 } 967 968 count = r->ifcall.count; 969 offset = r->ifcall.offset; 970 if(a->ns < count+offset+1){ 971 a->s = erealloc(a->s, count+offset+1); 972 a->ns = count+offset; 973 a->s[a->ns] = '\0'; 974 } 975 memmove(a->s+offset, r->ifcall.data, count); 976 r->ofcall.count = count; 977 respond(r, nil); 978 } 979 980 static void 981 fsdestroyfid(Fid *fid) 982 { 983 Aux *a; 984 985 a = fid->aux; 986 if(a==nil) 987 return; 988 989 if(a->ispost && a->s) 990 nntppost(net, a->s); 991 992 free(a->s); 993 free(a); 994 } 995 996 Srv nntpsrv = { 997 .destroyfid= fsdestroyfid, 998 .attach= fsattach, 999 .clone= fsclone, 1000 .walk1= fswalk1, 1001 .open= fsopen, 1002 .read= fsread, 1003 .write= fswrite, 1004 .stat= fsstat, 1005 }; 1006 1007 void 1008 usage(void) 1009 { 1010 fprint(2, "usage: nntpsrv [-a] [-s service] [-m mtpt] [nntp.server]\n"); 1011 exits("usage"); 1012 } 1013 1014 void 1015 dumpgroups(Group *g, int ind) 1016 { 1017 int i; 1018 1019 print("%*s%s\n", ind*4, "", g->name); 1020 for(i=0; i<g->nkid; i++) 1021 dumpgroups(g->kid[i], ind+1); 1022 } 1023 1024 void 1025 main(int argc, char **argv) 1026 { 1027 int auth, x; 1028 char *mtpt, *service, *where, *user; 1029 Netbuf n; 1030 UserPasswd *up; 1031 1032 mtpt = "/mnt/news"; 1033 service = nil; 1034 memset(&n, 0, sizeof n); 1035 user = nil; 1036 auth = 0; 1037 ARGBEGIN{ 1038 case 'D': 1039 chatty9p++; 1040 break; 1041 case 'N': 1042 netdebug = 1; 1043 break; 1044 case 'a': 1045 auth = 1; 1046 break; 1047 case 'u': 1048 user = EARGF(usage()); 1049 break; 1050 case 's': 1051 service = EARGF(usage()); 1052 break; 1053 case 'm': 1054 mtpt = EARGF(usage()); 1055 break; 1056 default: 1057 usage(); 1058 }ARGEND 1059 1060 if(argc > 1) 1061 usage(); 1062 if(argc==0) 1063 where = "$nntp"; 1064 else 1065 where = argv[0]; 1066 1067 now = time(0); 1068 1069 net = &n; 1070 if(auth) { 1071 n.auth = 1; 1072 if(user) 1073 up = auth_getuserpasswd(auth_getkey, "proto=pass service=nntp server=%q user=%q", where, user); 1074 else 1075 up = auth_getuserpasswd(auth_getkey, "proto=pass service=nntp server=%q", where); 1076 if(up == nil) 1077 sysfatal("no password: %r"); 1078 1079 n.user = up->user; 1080 n.pass = up->passwd; 1081 } 1082 1083 n.addr = netmkaddr(where, "tcp", "nntp"); 1084 1085 root = emalloc(sizeof *root); 1086 root->name = estrdup(""); 1087 root->parent = root; 1088 1089 n.fd = -1; 1090 if(nntpconnect(&n) < 0) 1091 sysfatal("nntpconnect: %s", n.response); 1092 1093 x=netdebug; 1094 netdebug=0; 1095 nntprefreshall(&n); 1096 netdebug=x; 1097 // dumpgroups(root, 0); 1098 1099 postmountsrv(&nntpsrv, service, mtpt, MREPL); 1100 exits(nil); 1101 } 1102 1103