xref: /plan9/sys/src/cmd/exportfs/exportfs.c (revision a960ed1cec55a25d7c4ccbf9707a04427b3c30d1)
1 /*
2  * exportfs - Export a plan 9 name space across a network
3  */
4 #include <u.h>
5 #include <libc.h>
6 #include <auth.h>
7 #include <fcall.h>
8 #include <libsec.h>
9 #define Extern
10 #include "exportfs.h"
11 
12 #define QIDPATH	((1LL<<48)-1)
13 vlong newqid = 0;
14 
15 enum {
16 	Encnone,
17 	Encssl,
18 	Enctls,
19 };
20 
21 void (*fcalls[])(Fsrpc*) =
22 {
23 	[Tversion]	Xversion,
24 	[Tauth]	Xauth,
25 	[Tflush]	Xflush,
26 	[Tattach]	Xattach,
27 	[Twalk]		Xwalk,
28 	[Topen]		slave,
29 	[Tcreate]	Xcreate,
30 	[Tclunk]	Xclunk,
31 	[Tread]		slave,
32 	[Twrite]	slave,
33 	[Tremove]	Xremove,
34 	[Tstat]		Xstat,
35 	[Twstat]	Xwstat,
36 };
37 
38 /* accounting and debugging counters */
39 int	filecnt;
40 int	freecnt;
41 int	qidcnt;
42 int	qfreecnt;
43 int	ncollision;
44 
45 int	netfd;
46 int	srvfd = -1;
47 int	nonone = 1;
48 char	*filterp;
49 char	*ealgs = "rc4_256 sha1";
50 char	*aanfilter = "/bin/aan";
51 int	encproto = Encnone;
52 
53 static void	mksecret(char *, uchar *);
54 static int localread9pmsg(int, void *, uint, ulong *);
55 static char *anstring  = "tcp!*!0";
56 int	filter(int, char *);
57 
58 void
59 usage(void)
60 {
61 	fprint(2, "usage: %s [-ads] [-f dbgfile] [-m msize] [-r root] [-S srvfile] [-e 'crypt hash'] [-A announce-string]\n", argv0);
62 	fatal("usage");
63 }
64 
65 void
66 main(int argc, char **argv)
67 {
68 	char buf[ERRMAX], ebuf[ERRMAX];
69 	Fsrpc *r;
70 	int n;
71 	char *dbfile, *srv, *file;
72 	AuthInfo *ai;
73 	ulong initial;
74 
75 	dbfile = "/tmp/exportdb";
76 	srv = nil;
77 	srvfd = -1;
78 
79 	ai = nil;
80 	ARGBEGIN{
81 	case 'a':
82 		/*
83 		 * We use p9any so we don't have to visit this code again, with the
84 		 * cost that this code is incompatible with the old world, which
85 		 * requires p9sk2. (The two differ in who talks first, so compatibility
86 		 * is awkward.)
87 		 */
88 		ai = auth_proxy(0, auth_getkey, "proto=p9any role=server");
89 		if(ai == nil)
90 			fatal("auth_proxy: %r");
91 		if(nonone && strcmp(ai->cuid, "none") == 0)
92 			fatal("exportfs by none disallowed");
93 		if(auth_chuid(ai, nil) < 0)
94 			fatal("auth_chuid: %r");
95 		if(newns(ai->cuid, 0) < 0)
96 			fatal("newns");
97 		putenv("service", "exportfs");
98 		break;
99 
100 	case 'e':
101 		ealgs = ARGF();
102 		if(ealgs == nil)
103 			usage();
104 		if(*ealgs == 0 || strcmp(ealgs, "clear") == 0)
105 			ealgs = nil;
106 		break;
107 
108 	case 'S':
109 		if(srvfd != -1)
110 			usage();
111 		file = EARGF(usage());
112 		if((srvfd = open(file, ORDWR)) < 0)
113 			sysfatal("open '%s': %r", file);
114 		break;
115 
116 	case 'd':
117 		dbg++;
118 		break;
119 
120 	case 'f':
121 		dbfile = EARGF(usage());
122 		break;
123 
124 	case 'F':
125 		/* accepted but ignored, for backwards compatibility */
126 		break;
127 
128 	case 'm':
129 		messagesize = strtoul(EARGF(usage()), nil, 0);
130 		break;
131 
132 	case 'r':
133 		srv = EARGF(usage());
134 		break;
135 
136 	case 's':
137 		srv = "/";
138 		break;
139 
140 	case 'p':
141 		patternfile = EARGF(usage());
142 		break;
143 
144 	case 'A':
145 		anstring = EARGF(usage());
146 		break;
147 
148 	default:
149 		usage();
150 	}ARGEND
151 	USED(argc, argv);
152 
153 	exclusions();
154 
155 	if(dbg) {
156 		n = create(dbfile, OWRITE|OTRUNC, 0666);
157 		dup(n, DFD);
158 		close(n);
159 	}
160 
161 	if(srvfd >= 0 && srv){
162 		fprint(2, "exportfs: -S cannot be used with -r or -s\n");
163 		usage();
164 	}
165 
166 	DEBUG(DFD, "exportfs: started\n");
167 
168 	rfork(RFNOTEG);
169 
170 	if(messagesize == 0){
171 		messagesize = iounit(netfd);
172 		if(messagesize == 0)
173 			messagesize = 8192+IOHDRSZ;
174 	}
175 
176 	Workq = emallocz(sizeof(Fsrpc)*Nr_workbufs);
177 //	for(i=0; i<Nr_workbufs; i++)
178 //		Workq[i].buf = emallocz(messagesize);
179 	fhash = emallocz(sizeof(Fid*)*FHASHSIZE);
180 
181 	fmtinstall('F', fcallfmt);
182 
183 	/*
184 	 * Get tree to serve from network connection,
185 	 * check we can get there and ack the connection
186  	 */
187 	if(srvfd != -1) {
188 		/* do nothing */
189 	}
190 	else if(srv) {
191 		chdir(srv);
192 		DEBUG(DFD, "invoked as server for %s", srv);
193 		strncpy(buf, srv, sizeof buf);
194 	}
195 	else {
196 		buf[0] = 0;
197 		n = read(0, buf, sizeof(buf)-1);
198 		if(n < 0) {
199 			errstr(buf, sizeof buf);
200 			fprint(0, "read(0): %s", buf);
201 			DEBUG(DFD, "read(0): %s", buf);
202 			exits(buf);
203 		}
204 		buf[n] = 0;
205 		if(chdir(buf) < 0) {
206 			errstr(ebuf, sizeof ebuf);
207 			fprint(0, "chdir(%d:\"%s\"): %s", n, buf, ebuf);
208 			DEBUG(DFD, "chdir(%d:\"%s\"): %s", n, buf, ebuf);
209 			exits(ebuf);
210 		}
211 	}
212 
213 	DEBUG(DFD, "initing root\n");
214 	initroot();
215 
216 	DEBUG(DFD, "exportfs: %s\n", buf);
217 
218 	if(srv == nil && srvfd == -1 && write(0, "OK", 2) != 2)
219 		fatal("open ack write");
220 
221 	if (readn(netfd, &initial, sizeof(ulong)) < sizeof(ulong))
222 		fatal("can't read initial string: %r\n");
223 
224 	if (!strncmp((char *)&initial, "impo", sizeof(ulong))) {
225 		char buf[128], *p, *args[3];
226 
227 		// New import.  Read import's parameters...
228 		initial = 0;
229 
230 		p = buf;
231 		while (p - buf < sizeof buf) {
232 			if ((n = read(netfd, p, 1)) < 0)
233 				fatal("can't read impo arguments: %r\n");
234 
235 			if (n == 0)
236 				fatal("connection closed while reading arguments\n");
237 
238 			if (*p == '\n')
239 				*p = '\0';
240 			if (*p++ == '\0')
241 				break;
242 		}
243 
244 		if (tokenize(buf, args, nelem(args)) != 2)
245 			fatal("impo arguments invalid: impo%s...\n", buf);
246 
247 		if (!strcmp(args[0], "aan"))
248 			filterp = aanfilter;
249 		else if (strcmp(args[0], "nofilter"))
250 			fatal("import filter argument unsupported: %s\n", args[0]);
251 
252 		if (!strcmp(args[1], "ssl"))
253 			encproto = Encssl;
254 		else if (!strcmp(args[1], "tls"))
255 			encproto = Enctls;
256 		else if (strcmp(args[1], "clear"))
257 			fatal("import encryption proto unsupported: %s\n", args[1]);
258 
259 		if (encproto == Enctls)
260 			sysfatal("%s: tls has not yet been implemented\n", argv[0]);
261 	}
262 
263 	if (encproto != Encnone && ealgs && ai) {
264 		uchar key[16];
265 		uchar digest[SHA1dlen];
266 		char fromclientsecret[21];
267 		char fromserversecret[21];
268 		int i;
269 
270 		memmove(key+4, ai->secret, ai->nsecret);
271 
272 		/* exchange random numbers */
273 		srand(truerand());
274 		for(i = 0; i < 4; i++)
275 			key[i+12] = rand();
276 
277 		if (initial)
278 			fatal("Protocol botch: old import\n");
279 		if(readn(netfd, key, 4) != 4)
280 			fatal("can't read key part; %r\n");
281 
282 		if(write(netfd, key+12, 4) != 4)
283 			fatal("can't write key part; %r\n");
284 
285 		/* scramble into two secrets */
286 		sha1(key, sizeof(key), digest, nil);
287 		mksecret(fromclientsecret, digest);
288 		mksecret(fromserversecret, digest+10);
289 
290 		if (filterp)
291 			netfd = filter(netfd, filterp);
292 
293 		switch (encproto) {
294 		case Encssl:
295 			netfd = pushssl(netfd, ealgs, fromserversecret,
296 						fromclientsecret, nil);
297 			break;
298 		case Enctls:
299 		default:
300 			fatal("Unsupported encryption protocol\n");
301 		}
302 
303 		if(netfd < 0)
304 			fatal("can't establish ssl connection: %r");
305 	}
306 	else if (filterp) {
307 		if (initial)
308 			fatal("Protocol botch: don't know how to deal with this\n");
309 		netfd = filter(netfd, filterp);
310 	}
311 
312 	/*
313 	 * Start serving file requests from the network
314 	 */
315 	for(;;) {
316 		r = getsbuf();
317 		if(r == 0)
318 			fatal("Out of service buffers");
319 
320 		n = localread9pmsg(netfd, r->buf, messagesize, &initial);
321 		if(n <= 0)
322 			fatal(nil);
323 
324 		if(convM2S(r->buf, n, &r->work) == 0)
325 			fatal("convM2S format error");
326 
327 		DEBUG(DFD, "%F\n", &r->work);
328 		(fcalls[r->work.type])(r);
329 	}
330 }
331 
332 // WARNING: Replace this with the original version as soon as all
333 // _old_ imports have been replaced with negotiating imports.  Also
334 // cpu relies on this (which needs to be fixed!) -- pb.
335 static int
336 localread9pmsg(int fd, void *abuf, uint n, ulong *initial)
337 {
338 	int m, len;
339 	uchar *buf;
340 
341 	buf = abuf;
342 
343 	/* read count */
344 	assert(BIT32SZ == sizeof(ulong));
345 	if (*initial) {
346 		memcpy(buf, initial, BIT32SZ);
347 		*initial = 0;
348 	}
349 	else {
350 		m = readn(fd, buf, BIT32SZ);
351 		if(m != BIT32SZ){
352 			if(m < 0)
353 				return -1;
354 			return 0;
355 		}
356 	}
357 
358 	len = GBIT32(buf);
359 	if(len <= BIT32SZ || len > n){
360 		werrstr("bad length in 9P2000 message header");
361 		return -1;
362 	}
363 	len -= BIT32SZ;
364 	m = readn(fd, buf+BIT32SZ, len);
365 	if(m < len)
366 		return 0;
367 	return BIT32SZ+m;
368 }
369 void
370 reply(Fcall *r, Fcall *t, char *err)
371 {
372 	uchar *data;
373 	int n;
374 
375 	t->tag = r->tag;
376 	t->fid = r->fid;
377 	if(err) {
378 		t->type = Rerror;
379 		t->ename = err;
380 	}
381 	else
382 		t->type = r->type + 1;
383 
384 	DEBUG(DFD, "\t%F\n", t);
385 
386 	data = malloc(messagesize);	/* not mallocz; no need to clear */
387 	if(data == nil)
388 		fatal(Enomem);
389 	n = convS2M(t, data, messagesize);
390 	if(write(netfd, data, n)!=n)
391 {syslog(0, "exportfs", "short write: %r");
392 		fatal("mount write");
393 }
394 	free(data);
395 }
396 
397 Fid *
398 getfid(int nr)
399 {
400 	Fid *f;
401 
402 	for(f = fidhash(nr); f; f = f->next)
403 		if(f->nr == nr)
404 			return f;
405 
406 	return 0;
407 }
408 
409 int
410 freefid(int nr)
411 {
412 	Fid *f, **l;
413 	char buf[128];
414 
415 	l = &fidhash(nr);
416 	for(f = *l; f; f = f->next) {
417 		if(f->nr == nr) {
418 			if(f->mid) {
419 				sprint(buf, "/mnt/exportfs/%d", f->mid);
420 				unmount(0, buf);
421 				psmap[f->mid] = 0;
422 			}
423 			if(f->f) {
424 				freefile(f->f);
425 				f->f = nil;
426 			}
427 			if(f->dir){
428 				free(f->dir);
429 				f->dir = nil;
430 			}
431 			*l = f->next;
432 			f->next = fidfree;
433 			fidfree = f;
434 			return 1;
435 		}
436 		l = &f->next;
437 	}
438 
439 	return 0;
440 }
441 
442 Fid *
443 newfid(int nr)
444 {
445 	Fid *new, **l;
446 	int i;
447 
448 	l = &fidhash(nr);
449 	for(new = *l; new; new = new->next)
450 		if(new->nr == nr)
451 			return 0;
452 
453 	if(fidfree == 0) {
454 		fidfree = emallocz(sizeof(Fid) * Fidchunk);
455 
456 		for(i = 0; i < Fidchunk-1; i++)
457 			fidfree[i].next = &fidfree[i+1];
458 
459 		fidfree[Fidchunk-1].next = 0;
460 	}
461 
462 	new = fidfree;
463 	fidfree = new->next;
464 
465 	memset(new, 0, sizeof(Fid));
466 	new->next = *l;
467 	*l = new;
468 	new->nr = nr;
469 	new->fid = -1;
470 	new->mid = 0;
471 
472 	return new;
473 }
474 
475 Fsrpc *
476 getsbuf(void)
477 {
478 	static int ap;
479 	int look, rounds;
480 	Fsrpc *wb;
481 	int small_instead_of_fast = 1;
482 
483 	if(small_instead_of_fast)
484 		ap = 0;	/* so we always start looking at the beginning and reuse buffers */
485 
486 	for(rounds = 0; rounds < 10; rounds++) {
487 		for(look = 0; look < Nr_workbufs; look++) {
488 			if(++ap == Nr_workbufs)
489 				ap = 0;
490 			if(Workq[ap].busy == 0)
491 				break;
492 		}
493 
494 		if(look == Nr_workbufs){
495 			sleep(10 * rounds);
496 			continue;
497 		}
498 
499 		wb = &Workq[ap];
500 		wb->pid = 0;
501 		wb->canint = 0;
502 		wb->flushtag = NOTAG;
503 		wb->busy = 1;
504 		if(wb->buf == nil)	/* allocate buffers dynamically to keep size down */
505 			wb->buf = emallocz(messagesize);
506 		return wb;
507 	}
508 	fatal("No more work buffers");
509 	return nil;
510 }
511 
512 void
513 freefile(File *f)
514 {
515 	File *parent, *child;
516 
517 Loop:
518 	f->ref--;
519 	if(f->ref > 0)
520 		return;
521 	freecnt++;
522 	if(f->ref < 0) abort();
523 	DEBUG(DFD, "free %s\n", f->name);
524 	/* delete from parent */
525 	parent = f->parent;
526 	if(parent->child == f)
527 		parent->child = f->childlist;
528 	else{
529 		for(child=parent->child; child->childlist!=f; child=child->childlist)
530 			if(child->childlist == nil)
531 				fatal("bad child list");
532 		child->childlist = f->childlist;
533 	}
534 	freeqid(f->qidt);
535 	free(f->name);
536 	f->name = nil;
537 	free(f);
538 	f = parent;
539 	if(f != nil)
540 		goto Loop;
541 }
542 
543 File *
544 file(File *parent, char *name)
545 {
546 	Dir *dir;
547 	char *path;
548 	File *f;
549 
550 	DEBUG(DFD, "\tfile: 0x%p %s name %s\n", parent, parent->name, name);
551 
552 	path = makepath(parent, name);
553 	if(patternfile != nil && excludefile(path)){
554 		free(path);
555 		return nil;
556 	}
557 	dir = dirstat(path);
558 	free(path);
559 	if(dir == nil)
560 		return nil;
561 
562 	for(f = parent->child; f; f = f->childlist)
563 		if(strcmp(name, f->name) == 0)
564 			break;
565 
566 	if(f == nil){
567 		f = emallocz(sizeof(File));
568 		f->name = estrdup(name);
569 
570 		f->parent = parent;
571 		f->childlist = parent->child;
572 		parent->child = f;
573 		parent->ref++;
574 		f->ref = 0;
575 		filecnt++;
576 	}
577 	f->ref++;
578 	f->qid.type = dir->qid.type;
579 	f->qid.vers = dir->qid.vers;
580 	f->qidt = uniqueqid(dir);
581 	f->qid.path = f->qidt->uniqpath;
582 
583 	f->inval = 0;
584 
585 	free(dir);
586 
587 	return f;
588 }
589 
590 void
591 initroot(void)
592 {
593 	Dir *dir;
594 
595 	root = emallocz(sizeof(File));
596 	root->name = estrdup(".");
597 
598 	dir = dirstat(root->name);
599 	if(dir == nil)
600 		fatal("root stat");
601 
602 	root->ref = 1;
603 	root->qid.vers = dir->qid.vers;
604 	root->qidt = uniqueqid(dir);
605 	root->qid.path = root->qidt->uniqpath;
606 	root->qid.type = QTDIR;
607 	free(dir);
608 
609 	psmpt = emallocz(sizeof(File));
610 	psmpt->name = estrdup("/");
611 
612 	dir = dirstat(psmpt->name);
613 	if(dir == nil)
614 		return;
615 
616 	psmpt->ref = 1;
617 	psmpt->qid.vers = dir->qid.vers;
618 	psmpt->qidt = uniqueqid(dir);
619 	psmpt->qid.path = psmpt->qidt->uniqpath;
620 	free(dir);
621 
622 	psmpt = file(psmpt, "mnt");
623 	if(psmpt == 0)
624 		return;
625 	psmpt = file(psmpt, "exportfs");
626 }
627 
628 char*
629 makepath(File *p, char *name)
630 {
631 	int i, n;
632 	char *c, *s, *path, *seg[256];
633 
634 	seg[0] = name;
635 	n = strlen(name)+2;
636 	for(i = 1; i < 256 && p; i++, p = p->parent){
637 		seg[i] = p->name;
638 		n += strlen(p->name)+1;
639 	}
640 	path = malloc(n);
641 	if(path == nil)
642 		fatal("out of memory");
643 	s = path;
644 
645 	while(i--) {
646 		for(c = seg[i]; *c; c++)
647 			*s++ = *c;
648 		*s++ = '/';
649 	}
650 	while(s[-1] == '/')
651 		s--;
652 	*s = '\0';
653 
654 	return path;
655 }
656 
657 int
658 qidhash(vlong path)
659 {
660 	int h, n;
661 
662 	h = 0;
663 	for(n=0; n<64; n+=Nqidbits){
664 		h ^= path;
665 		path >>= Nqidbits;
666 	}
667 	return h & (Nqidtab-1);
668 }
669 
670 void
671 freeqid(Qidtab *q)
672 {
673 	ulong h;
674 	Qidtab *l;
675 
676 	q->ref--;
677 	if(q->ref > 0)
678 		return;
679 	qfreecnt++;
680 	h = qidhash(q->path);
681 	if(qidtab[h] == q)
682 		qidtab[h] = q->next;
683 	else{
684 		for(l=qidtab[h]; l->next!=q; l=l->next)
685 			if(l->next == nil)
686 				fatal("bad qid list");
687 		l->next = q->next;
688 	}
689 	free(q);
690 }
691 
692 Qidtab*
693 qidlookup(Dir *d)
694 {
695 	ulong h;
696 	Qidtab *q;
697 
698 	h = qidhash(d->qid.path);
699 	for(q=qidtab[h]; q!=nil; q=q->next)
700 		if(q->type==d->type && q->dev==d->dev && q->path==d->qid.path)
701 			return q;
702 	return nil;
703 }
704 
705 int
706 qidexists(vlong path)
707 {
708 	int h;
709 	Qidtab *q;
710 
711 	for(h=0; h<Nqidtab; h++)
712 		for(q=qidtab[h]; q!=nil; q=q->next)
713 			if(q->uniqpath == path)
714 				return 1;
715 	return 0;
716 }
717 
718 Qidtab*
719 uniqueqid(Dir *d)
720 {
721 	ulong h;
722 	vlong path;
723 	Qidtab *q;
724 
725 	q = qidlookup(d);
726 	if(q != nil){
727 		q->ref++;
728 		return q;
729 	}
730 	path = d->qid.path;
731 	while(qidexists(path)){
732 		DEBUG(DFD, "collision on %s\n", d->name);
733 		/* collision: find a new one */
734 		ncollision++;
735 		path &= QIDPATH;
736 		++newqid;
737 		if(newqid >= (1<<16)){
738 			DEBUG(DFD, "collision wraparound\n");
739 			newqid = 1;
740 		}
741 		path |= newqid<<48;
742 		DEBUG(DFD, "assign qid %.16llux\n", path);
743 	}
744 	q = mallocz(sizeof(Qidtab), 1);
745 	if(q == nil)
746 		fatal("no memory for qid table");
747 	qidcnt++;
748 	q->ref = 1;
749 	q->type = d->type;
750 	q->dev = d->dev;
751 	q->path = d->qid.path;
752 	q->uniqpath = path;
753 	h = qidhash(d->qid.path);
754 	q->next = qidtab[h];
755 	qidtab[h] = q;
756 	return q;
757 }
758 
759 void
760 fatal(char *s, ...)
761 {
762 	char buf[ERRMAX];
763 	va_list arg;
764 	Proc *m;
765 
766 	if (s) {
767 		va_start(arg, s);
768 		vsnprint(buf, ERRMAX, s, arg);
769 		va_end(arg);
770 	}
771 
772 	/* Clear away the slave children */
773 	for(m = Proclist; m; m = m->next)
774 		postnote(PNPROC, m->pid, "kill");
775 
776 	DEBUG(DFD, "%s\n", buf);
777 	if (s)
778 		sysfatal(buf);
779 	else
780 		exits(nil);
781 }
782 
783 void*
784 emallocz(uint n)
785 {
786 	void *p;
787 
788 	p = mallocz(n, 1);
789 	if(p == nil)
790 		fatal(Enomem);
791 	return p;
792 }
793 
794 char*
795 estrdup(char *s)
796 {
797 	char *t;
798 
799 	t = strdup(s);
800 	if(t == nil)
801 		fatal(Enomem);
802 	return t;
803 }
804 
805 /* Network on fd1, mount driver on fd0 */
806 int
807 filter(int fd, char *cmd)
808 {
809 	int p[2], lfd, len, nb, argc;
810 	char newport[128], buf[128], devdir[40], *s, *file, *argv[16];
811 
812 	// Get a free port and post it to the client.
813 	if (announce(anstring, devdir) < 0)
814 		sysfatal("filter: Cannot announce %s: %r\n", anstring);
815 
816 	snprint(buf, sizeof(buf), "%s/local", devdir);
817 	buf[sizeof buf - 1] = '\0';
818 	if ((lfd = open(buf, OREAD)) < 0)
819 		sysfatal("filter: Cannot open %s: %r\n", buf);
820 	if ((len = read(lfd, newport, sizeof newport - 1)) < 0)
821 		sysfatal("filter: Cannot read %s: %r\n", buf);
822 	close(lfd);
823 	newport[len] = '\0';
824 
825 	if ((s = strchr(newport, '\n')) != nil)
826 		*s = '\0';
827 
828 	if ((nb = write(fd, newport, len)) < 0)
829 		sysfatal("getport; cannot write port; %r");
830 	assert(nb == len);
831 
832 	argc = tokenize(cmd, argv, nelem(argv)-2);
833 	if (argc == 0)
834 		sysfatal("filter: empty command");
835 	argv[argc++] = buf;
836 	argv[argc] = nil;
837 	file = argv[0];
838 	if (s = strrchr(argv[0], '/'))
839 		argv[0] = s+1;
840 
841 	if(pipe(p) < 0)
842 		fatal("pipe");
843 
844 	switch(rfork(RFNOWAIT|RFPROC|RFFDG)) {
845 	case -1:
846 		fatal("rfork record module");
847 	case 0:
848 		if (dup(p[0], 1) < 0)
849 			fatal("filter: Cannot dup to 1; %r\n");
850 		if (dup(p[0], 0) < 0)
851 			fatal("filter: Cannot dup to 0; %r\n");
852 		close(p[0]);
853 		close(p[1]);
854 		exec(file, argv);
855 		fatal("exec record module");
856 	default:
857 		close(fd);
858 		close(p[0]);
859 	}
860 	return p[1];
861 }
862 
863 static void
864 mksecret(char *t, uchar *f)
865 {
866 	sprint(t, "%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux",
867 		f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7], f[8], f[9]);
868 }
869