nntpfs.c 19 KB

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