nntpfs.c 18 KB

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