/*
 * nfsfh.c -- DF-2251 end-to-end consumer probe (runs ON the guest).
 *
 * Minimal raw ONC-RPC/TCP client: rpcbind GETPORT -> mountd MNT ->
 * nfsd LOOKUP, printing the ffs filehandle (struct ufid) bytes for the
 * requested names.  The last 4 bytes of a 16-byte ffs FH are ufid_gen
 * (big endian) -- the i_gen value drawn from krandom() at first-time
 * inode allocation (ffs_alloc.c:642).
 *
 * This mimics exactly what any NFS client host (authorized, possibly
 * untrusted) observes on the wire.  Build: cc -O -o nfsfh nfsfh.c
 *
 * usage: ./nfsfh <export-path> <name> [<name> ...]
 *    or: ./nfsfh <export-path> -   (names on stdin, one per line)
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>

static uint32_t xid_ctr = 0x4e465300;

static void
put32(uint8_t *b, size_t *o, uint32_t v)
{
	b[(*o)++] = v >> 24; b[(*o)++] = v >> 16;
	b[(*o)++] = v >> 8;  b[(*o)++] = v;
}

static void
putstr(uint8_t *b, size_t *o, const char *s)
{
	size_t l = strlen(s), pad = (4 - l % 4) % 4;

	put32(b, o, (uint32_t)l);
	memcpy(b + *o, s, l);
	*o += l;
	memset(b + *o, 0, pad);
	*o += pad;
}

static uint32_t
get32(const uint8_t *b, size_t *o)
{
	uint32_t v = ((uint32_t)b[*o] << 24) | ((uint32_t)b[*o+1] << 16) |
	    ((uint32_t)b[*o+2] << 8) | (uint32_t)b[*o+3];

	*o += 4;
	return (v);
}

static int
tcpconn(uint16_t port)
{
	struct sockaddr_in sa;
	int fd, one = 1;

	fd = socket(AF_INET, SOCK_STREAM, 0);
	if (fd < 0) { perror("socket"); exit(1); }
	{
		/* observers are root: bind a reserved source port like any
		 * real mount client (mountd rejects non-privileged ports) */
		struct sockaddr_in la;
		int p;

		for (p = 1023; p >= 512; p--) {
			memset(&la, 0, sizeof(la));
			la.sin_family = AF_INET;
			la.sin_port = htons((uint16_t)p);
			la.sin_addr.s_addr = inet_addr("127.0.0.1");
			if (bind(fd, (struct sockaddr *)&la, sizeof(la)) == 0)
				break;
		}
	}
	memset(&sa, 0, sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_port = htons(port);
	sa.sin_addr.s_addr = inet_addr("127.0.0.1");
	if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
		perror("connect"); exit(1);
	}
	setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
	{
		struct timeval tv;

		tv.tv_sec = 10;
		tv.tv_usec = 0;
		setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
	}
	return (fd);
}

static int
send_call(int fd, uint32_t prog, uint32_t vers, uint32_t proc,
    const uint8_t *args, size_t alen)
{
	uint8_t rec[4096], hdr[4];
	size_t o = 0;
	uint32_t xid = ++xid_ctr;

	put32(rec, &o, xid);
	put32(rec, &o, 0);		/* CALL */
	put32(rec, &o, 2);		/* rpcvers */
	put32(rec, &o, prog);
	put32(rec, &o, vers);
	put32(rec, &o, proc);
	{
		/* AUTH_SYS (flavor 1) credentials: stamp, hostname, uid, gid */
		uint8_t body[128];
		size_t bo = 0, blen;
		char host[64];

		if (gethostname(host, sizeof(host)) != 0)
			strlcpy(host, "observer", sizeof(host));
		put32(body, &bo, 0);
		putstr(body, &bo, host);
		put32(body, &bo, 0);	/* uid */
		put32(body, &bo, 0);	/* gid */
		put32(body, &bo, 0);	/* no extra gids */
		blen = (bo + 3) & ~3;
		while (bo < blen)
			body[bo++] = 0;
		put32(rec, &o, 1);		/* AUTH_SYS */
		put32(rec, &o, (uint32_t)blen);	/* cred length */
		memcpy(rec + o, body, blen);
		o += blen;
	}
	put32(rec, &o, 0); put32(rec, &o, 0);	/* verf  auth_null */
	if (alen)
		memcpy(rec + o, args, alen);
	o += alen;

	hdr[0] = 0x80;			/* last fragment */
	hdr[1] = (o >> 16) & 0xff; hdr[2] = (o >> 8) & 0xff; hdr[3] = o & 0xff;
	if (write(fd, hdr, 4) != 4) { perror("write"); exit(1); }
	if (write(fd, rec, o) != (ssize_t)o) { perror("write"); exit(1); }
	return (0);
}

static int
readn(int fd, void *buf, size_t n)
{
	uint8_t *p = buf;

	while (n) {
		ssize_t r = read(fd, p, n);

		if (r <= 0)
			return (-1);
		p += r;
		n -= r;
	}
	return (0);
}

static size_t
recv_reply(int fd, uint8_t *buf, size_t bufsz)
{
	uint8_t lhdr[4];
	uint32_t seg;
	size_t tot = 0;

	for (;;) {
		if (readn(fd, lhdr, 4)) { perror("read"); exit(1); }
		seg = ((uint32_t)lhdr[0] & 0x7f) << 24 |
		    (uint32_t)lhdr[1] << 16 | (uint32_t)lhdr[2] << 8 | lhdr[3];
		if (seg > bufsz - tot) { fprintf(stderr, "frag too big\n"); exit(1); }
		if (readn(fd, buf + tot, seg)) { perror("read"); exit(1); }
		tot += seg;
		if (lhdr[0] & 0x80)
			break;
	}
	return (tot);
}

static uint16_t
rpcbind_getport(void)
{
	FILE *fp;
	char line[256];
	uint16_t port = 0;

	/* observer-style: ask rpcinfo (rpcbind) where mountd (100005 v3) is */
	fp = popen("rpcinfo -p 127.0.0.1", "r");
	if (fp == NULL)
		return (0);
	while (fgets(line, sizeof(line), fp) != NULL) {
		unsigned long prog, vers;
		char prot[8];
		unsigned long p;

		if (sscanf(line, "%lu %lu %7s %lu", &prog, &vers, prot, &p) == 4
		    && prog == 100005 && vers == 3 && strcmp(prot, "tcp") == 0) {
			port = (uint16_t)p;
			break;
		}
	}
	pclose(fp);
	return (port);
}

static size_t
mount_mnt(int fd, const char *path, uint8_t *fhout)
{
	uint8_t args[512], rep[2048];
	size_t o = 0;
	uint32_t st, fhlen;

	o = 0;
	putstr(args, &o, path);
	send_call(fd, 100005, 3, 1, args, o);
	{
		size_t n = recv_reply(fd, rep, sizeof(rep));
		size_t di;

		fprintf(stderr, "# mnt reply (%zu bytes):", n);
		for (di = 0; di < n && di < 64; di++)
			fprintf(stderr, " %02x", rep[di]);
		fprintf(stderr, "\n");
	}
	o = 4 + 4 + 4 + 8;
	get32(rep, &o);		/* accept_stat */
	st = get32(rep, &o);
	if (st != 0) {
		fprintf(stderr, "MNT status %u\n", st);
		exit(1);
	}
	fhlen = get32(rep, &o);
	if (fhlen < 8 || fhlen > 1024) {
		fprintf(stderr, "bad fhlen %u\n", fhlen);
		exit(1);
	}
	memcpy(fhout, rep + o, fhlen);
	return (fhlen);
}

static size_t
nfs_lookup(int fd, const uint8_t *dirfh, size_t fhlen, const char *name,
    uint8_t *fhout)
{
	uint8_t args[1200], rep[2048];
	size_t o = 0;
	uint32_t st, attr_true, l;
	int i;

	put32(args, &o, (uint32_t)fhlen);
	memcpy(args + o, dirfh, fhlen);
	o += fhlen;
	if (fhlen % 4)
		o += 4 - fhlen % 4;
	putstr(args, &o, name);
	send_call(fd, 100003, 3, 3, args, o);
	recv_reply(fd, rep, sizeof(rep));
	o = 4 + 4 + 4 + 8;
	get32(rep, &o);		/* accept_stat */
	st = get32(rep, &o);
	if (st != 0) {
		fprintf(stderr, "LOOKUP %s: status %u\n", name, st);
		return (0);
	}
	attr_true = get32(rep, &o);
	if (attr_true)
		o += 92;		/* fattr3 */
	/* fallback scans if the layout assumption drifts */
	l = get32(rep, &o);
	if (l < 8 || l > 64) {
		for (i = 0; i < 40; i += 4) {
			size_t t = 4 + 4 + 4 + 8 + 4 + 4 + i;

			l = get32(rep, &t);
			if (l >= 8 && l <= 64) {
				o = t;
				break;
			}
		}
	}
	if (l < 8 || l > 64) {
		fprintf(stderr, "LOOKUP %s: cannot locate fh (len=%u)\n",
		    name, l);
		return (0);
	}
	memcpy(fhout, rep + o, l);
	return (l);
}

static void
printfh(const char *name, const uint8_t *fh, size_t l)
{
	size_t i;
	uint64_t ino = 0;
	uint32_t gen = 0;

	/* guest-observed DF ffs fh layout (28 bytes):
	 *   [0..8)   fsid (opaque)
	 *   [8..10)  ufid_len LE (=0x18)
	 *   [16..24) ufid_ino LE64
	 *   [24..28) ufid_gen LE32  <- i_gen drawn from krandom() */
	if (l >= 28) {
		for (i = 23; i >= 16; i--)
			ino = (ino << 8) | fh[i];
		for (i = 27; i >= 24; i--)
			gen = (gen << 8) | fh[i];
	}
	printf("FH %s len=%zu hex=", name, l);
	for (i = 0; i < l; i++)
		printf("%02x", fh[i]);
	printf(" INO=%llu GEN=%u\n", (unsigned long long)ino, gen);
}

int
main(int argc, char **argv)
{
	uint8_t rootfh[1088], fh[1088];
	uint16_t mport;
	size_t rl, l;
	int nfd;
	char line[512];

	if (argc < 3) {
		fprintf(stderr, "usage: %s <export-path> <name>|- [names...]\n",
		    argv[0]);
		return (1);
	}
	mport = rpcbind_getport();
	fprintf(stderr, "# mountd port %u\n", (unsigned)mport);
	{
		int mfd = tcpconn(mport);

		rl = mount_mnt(mfd, argv[1], rootfh);
		close(mfd);
	}
	printfh("<EXPORT-ROOT>", rootfh, rl);
	nfd = tcpconn(2049);
	if (strcmp(argv[2], "-") == 0) {
		while (fgets(line, sizeof(line), stdin) != NULL) {
			size_t ll = strlen(line);

			while (ll && (line[ll-1] == '\n' || line[ll-1] == '\r'))
				line[--ll] = 0;
			if (ll == 0)
				continue;
			l = nfs_lookup(nfd, rootfh, rl, line, fh);
			if (l)
				printfh(line, fh, l);
		}
	} else {
		int i;

		for (i = 2; i < argc; i++) {
			l = nfs_lookup(nfd, rootfh, rl, argv[i], fh);
			if (l)
				printfh(argv[i], fh, l);
		}
	}
	close(nfd);
	return (0);
}
