nntpfs.c 19 KB

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