Warning: Attempt to read property "date" on null in /usr/local/www/websvn.planix.org/blame.php on line 247

Warning: Attempt to read property "msg" on null in /usr/local/www/websvn.planix.org/blame.php on line 247
WebSVN – planix.SVN – Blame – /os/branches/feature_posix/sys/src/cmd/nntpfs.c – Rev 2

Subversion Repositories planix.SVN

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 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