/*#define	DEBUG 1 */
/*
 * $Id: rpcfilter.c,v 1.25 2010/04/22 15:56:22 c4chris Exp $
 */

#include "os.h"

/*
 * rpcfilter.c - filter RPC packets.
 *
 * David A. Curry				Jeffrey C. Mogul
 * Purdue University				Digital Equipment Corporation
 * Engineering Computer Network			Western Research Laboratory
 * 1285 Electrical Engineering Building		250 University Avenue
 * West Lafayette, IN 47907-1285		Palo Alto, CA 94301
 * davy@ecn.purdue.edu				mogul@decwrl.dec.com
 * 
 */

/*
 * TODO: replace the gethostbyaddr() functions with getnameinfo()
 *
 */

#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#ifdef SVR4
#include <sys/tiuser.h>
#include <sys/sysmacros.h>
#endif
#ifdef USE_LINUX
/* This yucky hack is needed because glibc's headers are wrong wrt the
 * correct size needed in RPC stuff on 64-bit machines.  There even is
 * a comment in <rpc/types.h> to that effect...  */
#define u_long uint32_t
#endif
#include <rpc/types.h>
#include <rpc/xdr.h>
#include <rpc/auth.h>
#include <rpc/clnt.h>
#include <rpc/rpc_msg.h>
#include <rpc/pmap_clnt.h>
#include <rpc/pmap_prot.h>
#include <rpc/svc.h>
#ifdef USE_LINUX
#undef u_long
#endif
#include <netdb.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <math.h>
#include <pwd.h>
#include <stdlib.h>

#define NFSSERVER	1

#ifdef sun
#include <sys/vfs.h>
#include <nfs/nfs.h>
#endif

#ifdef ultrix
#include <sys/types.h>
#include <sys/time.h>
#include <nfs/nfs.h>
#endif

#ifdef sgi
#include <sys/time.h>
#ifdef IRIX6
#include <sys/vfs.h>
#include <sys/fs/nfs.h>
#else
#include "sgi.map.h"
#endif
#include <sys/sysmacros.h>
#endif

#if defined(__alpha) && !defined(LINUX)
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mount.h>
#include <nfs/nfs.h>
#endif

#ifdef LINUX
#include "linux.map.h"
#endif

#include "nfswatch.h"
#include "externs.h"
#include "rpcdefs.h"

#if defined(SUNOS54)
	/* SunOS 5.4 hides all of these behind #ifdef _KERNEL in <nfs/nfs.h> */
bool_t xdr_creatargs();
bool_t xdr_diropargs();
bool_t xdr_fhandle();
bool_t xdr_linkargs();
bool_t xdr_rddirargs();
bool_t xdr_readargs();
bool_t xdr_rnmargs();
bool_t xdr_saargs();
bool_t xdr_slargs();
bool_t xdr_writeargs();
#endif

/*
 * NFS procedure types and XDR argument decoding routines.
 */
static struct nfs_proc nfs_procs[] = {
/* RFS_NULL (0)		*/
	{ NFS_READ, (xdrproc_t) xdr_void,	0 },
/* RFS_GETATTR (1)	*/
	{ NFS_READ, (xdrproc_t) xdr_fhandle,	sizeof(fhandle_t) },
/* RFS_SETATTR (2)	*/
	{ NFS_WRITE, (xdrproc_t) xdr_saargs,	sizeof(struct nfssaargs) },
/* RFS_ROOT (3)		*/
	{ NFS_READ, (xdrproc_t) xdr_void,	0 },
/* RFS_LOOKUP (4)	*/
	{ NFS_READ, (xdrproc_t) xdr_diropargs,	sizeof(struct nfsdiropargs) },
/* RFS_READLINK (5)	*/
	{ NFS_READ, (xdrproc_t) xdr_fhandle,	sizeof(fhandle_t) },
/* RFS_READ (6)		*/
	{ NFS_READ, (xdrproc_t) xdr_readargs,	sizeof(struct nfsreadargs) },
/* RFS_WRITECACHE (7)	*/
	{ NFS_WRITE, (xdrproc_t) xdr_void,	0 },
/* RFS_WRITE (8)	*/
	{ NFS_WRITE, (xdrproc_t) xdr_writeargs,	sizeof(struct nfswriteargs) },
/* RFS_CREATE (9)	*/
	{ NFS_WRITE, (xdrproc_t) xdr_creatargs,	sizeof(struct nfscreatargs) },
/* RFS_REMOVE (10)	*/
	{ NFS_WRITE, (xdrproc_t) xdr_diropargs,	sizeof(struct nfsdiropargs) },
/* RFS_RENAME (11)	*/
	{ NFS_WRITE, (xdrproc_t) xdr_rnmargs,	sizeof(struct nfsrnmargs) },
/* RFS_LINK (12)	*/
	{ NFS_WRITE, (xdrproc_t) xdr_linkargs,	sizeof(struct nfslinkargs) },
/* RFS_SYMLINK (13)	*/
	{ NFS_WRITE, (xdrproc_t) xdr_slargs,	sizeof(struct nfsslargs) },
/* RFS_MKDIR (14)	*/
	{ NFS_WRITE, (xdrproc_t) xdr_creatargs,	sizeof(struct nfscreatargs) },
/* RFS_RMDIR (15)	*/
	{ NFS_WRITE, (xdrproc_t) xdr_diropargs,	sizeof(struct nfsdiropargs) },
/* RFS_READDIR (16)	*/
	{ NFS_READ, (xdrproc_t) xdr_rddirargs,	sizeof(struct nfsrddirargs) },
/* RFS_STATFS (17)	*/
	{ NFS_READ, (xdrproc_t) xdr_fhandle,	sizeof(fhandle_t) }
};

/*
 * NFS3 procedure types and XDR argument decoding routines.
 */
static struct nfs_proc nfs3_procs[] = {
/* NFSPROC3_NULL (0) */
	{ NFS_READ, (xdrproc_t) xdr_void,
	  0 },
/* NFSPROC3_GETATTR (1) */
	{ NFS_READ, (xdrproc_t) xdr_nfs_fh3,
	  sizeof(nfs_fh3) },
/* NFSPROC3_SETATTR (2) */
	{ NFS_WRITE, (xdrproc_t) xdr_nfs_fh3,
	  sizeof(nfs_fh3) },
/* NFSPROC3_LOOKUP (3) */
	{ NFS_READ, (xdrproc_t) xdr_diropargs3,
	  sizeof(diropargs3) },
/* NFSPROC3_ACCESS (4) */
	{ NFS_READ, (xdrproc_t) xdr_nfs_fh3,
	  sizeof(nfs_fh3) },
/* NFSPROC3_READLINK (5) */
	{ NFS_READ, (xdrproc_t) xdr_nfs_fh3,
	  sizeof(nfs_fh3) },
/* NFSPROC3_READ (6) */
	{ NFS_READ, (xdrproc_t) xdr_nfs_fh3,
	  sizeof(nfs_fh3) },
/* NFSPROC3_WRITE (7) */
	{ NFS_WRITE, (xdrproc_t) xdr_nfs_fh3,
	  sizeof(nfs_fh3) },
/* NFSPROC3_CREATE (8) */
	{ NFS_WRITE, (xdrproc_t) xdr_diropargs3,
	  sizeof(diropargs3) },
/* NFSPROC3_MKDIR (9) */
	{ NFS_WRITE, (xdrproc_t) xdr_diropargs3,
	  sizeof(diropargs3) },
/* NFSPROC3_SYMLINK (10) */
	{ NFS_WRITE, (xdrproc_t) xdr_diropargs3,
	  sizeof(diropargs3) },
/* NFSPROC3_MKNOD (11) */
	{ NFS_WRITE, (xdrproc_t) xdr_diropargs3,
	  sizeof(diropargs3) },
/* NFSPROC3_REMOVE (12) */
	{ NFS_WRITE, (xdrproc_t) xdr_diropargs3,
	  sizeof(diropargs3) },
/* NFSPROC3_RMDIR (13) */
	{ NFS_WRITE, (xdrproc_t) xdr_diropargs3,
	  sizeof(diropargs3) },
/* NFSPROC3_RENAME (14) */
	{ NFS_WRITE, (xdrproc_t) xdr_RENAME3args,
	  sizeof(RENAME3args) },
/* NFSPROC3_LINK (15) */
	{ NFS_WRITE, (xdrproc_t) xdr_LINK3args,
	  sizeof(LINK3args) },
/* NFSPROC3_READDIR (16) */
	{ NFS_READ, (xdrproc_t) xdr_nfs_fh3,
	  sizeof(nfs_fh3) },
/* NFSPROC3_READDIRPLUS (17) */
	{ NFS_READ, (xdrproc_t) xdr_nfs_fh3,
	  sizeof(nfs_fh3) },
/* NFSPROC3_FSSTAT (18) */
	{ NFS_READ, (xdrproc_t) xdr_nfs_fh3,
	  sizeof(nfs_fh3) },
/* NFSPROC3_FSINFO (19) */
	{ NFS_READ, (xdrproc_t) xdr_nfs_fh3,
	  sizeof(nfs_fh3) },
/* NFSPROC3_PATHCONF (20) */
	{ NFS_READ, (xdrproc_t) xdr_nfs_fh3,
	  sizeof(nfs_fh3) },
/* NFSPROC3_COMMIT (21) */
	{ NFS_WRITE, (xdrproc_t) xdr_nfs_fh3,
	  sizeof(nfs_fh3) }
};

NFSCall nfs_calls[NFSCALLHASHSIZE];

static void CountCallAuth(struct rpc_msg *);
static void CountSrc(ipaddrt);
static void rpc_callfilter(char *, u_int, ipaddrt, struct timeval *);
static void rpc_replyfilter(char *, ipaddrt, struct timeval *);
static void nfs_filter(char *, u_int, ipaddrt, struct timeval *);
static void nfs_count(fhandle_t *, int);
static void nfs3_count(nfs_fh3 *, int);
static void nfs_hash_call(int proto, struct rpc_msg *, ipaddrt, struct timeval *);
static void nfs_hash_reply(struct rpc_msg *, ipaddrt, struct timeval *);
int udprpc_recv(char *, u_int, struct rpc_msg *, SVCXPRT **);
static void nfs3_filter(char *, u_int, ipaddrt, struct timeval *);

/*
 * rpc_filter - pass off RPC packets to other filters.
 */
void
rpc_filter(char *data, u_int length, ipaddrt src, ipaddrt dst,
	   struct timeval *tstamp)
{
	register struct rpc_msg *msg;

	msg = (struct rpc_msg *) data;

	/*
	 * See which "direction" the packet is going.  We
	 * can classify RPC CALLs, but we cannot classify
	 * REPLYs, since they no longer have the RPC
	 * program number in them (sigh).
	 */
	switch (ntohl(msg->rm_direction)) {
	case CALL:			/* RPC call			*/
		rpc_callfilter(data, length, src, tstamp);
		break;
	case REPLY:			/* RPC reply			*/
		rpc_replyfilter(data, dst, tstamp);
		break;
	default:			/* probably not an RPC packet	*/
		break;
	}
}

/*
 * rpc_callfilter - filter RPC call packets.
 */
static void
rpc_callfilter(char *data, u_int length, ipaddrt src, struct timeval *tstamp)
{
	struct rpc_msg *msg = (struct rpc_msg *) data;
	uint32_t prog = ntohl(msg->rm_call.cb_prog);

	/*
	 * Decide what to do based on the program.
	 */
	switch (prog) {
	case RPC_NFSPROG: {
		int version = ntohl(msg->rm_call.cb_vers);
		if (version == 2)
			nfs_filter(data, length, src, tstamp);
		else if (version == 3)
			nfs3_filter(data, length, src, tstamp);
		else
			fprintf(stderr, "Unknown NFS version: %d\n", version);
		break;
	}
	case RPC_PMAPPROG:
		pkt_counters[PKT_PORTMAP].pc_interval++;
		pkt_counters[PKT_PORTMAP].pc_total++;
		break;
	case RPC_MOUNTPROG:
		pkt_counters[PKT_NFSMOUNT].pc_interval++;
		pkt_counters[PKT_NFSMOUNT].pc_total++;
		break;
#ifdef notdef
	case RPC_YPPROG:
	case RPC_YPBINDPROG:
	case RPC_YPPASSWDPROG:
	case RPC_YPUPDATEPROG:
	case RPC_NIS:
	case RPC_CACHEPROG:
	case RPC_CB_PROG:
	case RPC_RSTATPROG:
	case RPC_RUSERSPROG:
	case RPC_DBXPROG:
	case RPC_WALLPROG:
	case RPC_ETHERSTATPROG:
	case RPC_RQUOTAPROG:
	case RPC_SPRAYPROG:
	case RPC_IBM3270PROG:
	case RPC_IBMRJEPROG:
	case RPC_SELNSVCPROG:
	case RPC_RDATABASEPROG:
	case RPC_REXECPROG:
	case RPC_ALICEPROG:
	case RPC_SCHEDPROG:
	case RPC_LOCKPROG:
	case RPC_NETLOCKPROG:
	case RPC_X25PROG:
	case RPC_STATMON1PROG:
	case RPC_STATMON2PROG:
	case RPC_SELNLIBPROG:
	case RPC_BOOTPARAMPROG:
	case RPC_MAZEPROG:
	case RPC_KEYSERVEPROG:
	case RPC_SECURECMDPROG:
	case RPC_NETFWDIPROG:
	case RPC_NETFWDTPROG:
	case RPC_SUNLINKMAP_PROG:
	case RPC_NETMONPROG:
	case RPC_DBASEPROG:
	case RPC_PWDAUTHPROG:
	case RPC_TFSPROG:
	case RPC_NSEPROG:
	case RPC_NSE_ACTIVATE_PROG:
	case RPC_PCNFSDPROG:
	case RPC_PYRAMIDLOCKINGPROG:
	case RPC_PYRAMIDSYS5:
	case RPC_CADDS_IMAGE:
	case RPC_ADT_RFLOCKPROG:
#endif
	default:
		pkt_counters[PKT_OTHERRPC].pc_interval++;
		pkt_counters[PKT_OTHERRPC].pc_total++;
		break;
	}
}

/*
 * rpc_replyfilter - count RPC reply packets.
 */
static void
rpc_replyfilter(char *data, ipaddrt dst, struct timeval *tstamp)
{
	register struct rpc_msg *msg;

	msg = (struct rpc_msg *) data;

	pkt_counters[PKT_RPCAUTH].pc_interval++;
	pkt_counters[PKT_RPCAUTH].pc_total++;
	nfs_hash_reply(msg, dst, tstamp);
}

/*
 * nfs_filter - filter NFS packets.
 */
static void
nfs_filter(char *data, u_int length, ipaddrt src, struct timeval *tstamp)
{
	u_int proc;
	caddr_t args;
	SVCXPRT *xprt;
	struct rpc_msg msg;
	union nfs_rfsargs nfs_rfsargs;
	char cred_area[2*MAX_AUTH_BYTES];

	msg.rm_call.cb_cred.oa_base = cred_area;
	msg.rm_call.cb_verf.oa_base = &(cred_area[MAX_AUTH_BYTES]);

	/*
	 * Act as if we received this packet through RPC.
	 */
	if (!udprpc_recv(data, length, &msg, &xprt))
		return;

	/*
	 * Get the NFS procedure number.
	 */
	proc = msg.rm_call.cb_proc;

	if (proc >= MAXNFSPROC)
		return;

	CountCallAuth(&msg);
	nfs_hash_call(NFSv2, &msg, src, tstamp);

	/*
	 * Now decode the arguments to the procedure from
	 * XDR format.
	 */
	args = (caddr_t) &nfs_rfsargs;
	(void) bzero(args, nfs_procs[proc].nfs_argsz);

	if (!SVC_GETARGS(xprt, nfs_procs[proc].nfs_xdrargs, args))
		return;

	prc_counters[NFSv2][prc_countmap[NFSv2][proc]].pr_total++;
	prc_counters[NFSv2][prc_countmap[NFSv2][proc]].pr_interval++;

	CountSrc(src);

	/*
	 * Now count the packet in the appropriate file system's
	 * counters.
	 */
	switch (proc) {
	case RFS_NULL:
		break;
	case RFS_GETATTR:
		nfs_count(&nfs_rfsargs.fhandle, proc);
		break;
	case RFS_SETATTR:
		nfs_count(&nfs_rfsargs.nfssaargs.saa_fh, proc);
		break;
	case RFS_ROOT:
		break;
	case RFS_LOOKUP:
#if defined(SUNOS5) && defined(DA_FREENAME)	/* SunOS 2.2 and greater */
		nfs_count(&nfs_rfsargs.nfsdiropargs.da_fhandle_buf, proc);
#else
		nfs_count(&nfs_rfsargs.nfsdiropargs.da_fhandle, proc);
#endif
		break;
	case RFS_READLINK:
		nfs_count(&nfs_rfsargs.fhandle, proc);
		break;
	case RFS_READ:
		nfs_count(&nfs_rfsargs.nfsreadargs.ra_fhandle, proc);
		break;
	case RFS_WRITECACHE:
		break;
	case RFS_WRITE:
#if defined(SUNOS5) && defined(DA_FREENAME)	/* SunOS 2.2 and greater */
		nfs_count(&nfs_rfsargs.nfswriteargs.wa_args_buf.otw_wa_fhandle, proc);
#else
		nfs_count(&nfs_rfsargs.nfswriteargs.wa_fhandle, proc);
#endif
		break;
	case RFS_CREATE:
#if defined(SUNOS5) && defined(DA_FREENAME)	/* SunOS 2.2 and greater */
		nfs_count(&nfs_rfsargs.nfscreatargs.ca_da.da_fhandle_buf, proc);
#else
		nfs_count(&nfs_rfsargs.nfscreatargs.ca_da.da_fhandle, proc);
#endif
		break;
	case RFS_REMOVE:
#if defined(SUNOS5) && defined(DA_FREENAME)	/* SunOS 2.2 and greater */
		nfs_count(&nfs_rfsargs.nfsdiropargs.da_fhandle_buf, proc);
#else
		nfs_count(&nfs_rfsargs.nfsdiropargs.da_fhandle, proc);
#endif
		break;
	case RFS_RENAME:
#if defined(SUNOS5) && defined(DA_FREENAME)	/* SunOS 2.2 and greater */
		nfs_count(&nfs_rfsargs.nfsrnmargs.rna_from.da_fhandle_buf, proc);
#else
		nfs_count(&nfs_rfsargs.nfsrnmargs.rna_from.da_fhandle, proc);
#endif
		break;
	case RFS_LINK:
#if defined(SUNOS5) && defined(DA_FREENAME)	/* SunOS 2.2 and greater */
		nfs_count(&nfs_rfsargs.nfslinkargs.la_from_buf, proc);
#else
		nfs_count(&nfs_rfsargs.nfslinkargs.la_from, proc);
#endif
		break;
	case RFS_SYMLINK:
#if defined(SUNOS5) && defined(DA_FREENAME)	/* SunOS 2.2 and greater */
		nfs_count(&nfs_rfsargs.nfsslargs.sla_from.da_fhandle_buf, proc);
#else
		nfs_count(&nfs_rfsargs.nfsslargs.sla_from.da_fhandle, proc);
#endif
		break;
	case RFS_MKDIR:
#if defined(SUNOS5) && defined(DA_FREENAME)	/* SunOS 2.2 and greater */
		nfs_count(&nfs_rfsargs.nfscreatargs.ca_da.da_fhandle_buf, proc);
#else
		nfs_count(&nfs_rfsargs.nfscreatargs.ca_da.da_fhandle, proc);
#endif
		break;
	case RFS_RMDIR:
#if defined(SUNOS5) && defined(DA_FREENAME)	/* SunOS 2.2 and greater */
		nfs_count(&nfs_rfsargs.nfsdiropargs.da_fhandle_buf, proc);
#else
		nfs_count(&nfs_rfsargs.nfsdiropargs.da_fhandle, proc);
#endif
		break;
	case RFS_READDIR:
		nfs_count(&nfs_rfsargs.nfsrddirargs.rda_fh, proc);
		break;
	case RFS_STATFS:
		nfs_count(&nfs_rfsargs.fhandle, proc);
		break;
	}

	/*
	 * Decide whether it's a read or write process.
	 */
	switch (nfs_procs[proc].nfs_proctype) {
	case NFS_READ:
		pkt_counters[PKT_NFSREAD].pc_interval++;
		pkt_counters[PKT_NFSREAD].pc_total++;
		break;
	case NFS_WRITE:
		pkt_counters[PKT_NFSWRITE].pc_interval++;
		pkt_counters[PKT_NFSWRITE].pc_total++;
		break;
	}
}

/*
 * nfs_count - count an NFS reference to a specific file system.
 */
static NFSCounter *lastmatch = NULL;

static void
do_nfs_count(my_fsid fsid, ino_t ino, char *osname, char *sfsname,
	     int proc)
{
	int i, match1;

	/*
	 * Run through the NFS counters looking for the matching
	 * file system.
	 */
	match1 = 0;

	/* We use a 1-behind cache to speed the search */
	if (lastmatch) {
	    if (learnfs) {
		match1 = fsid_eq(lastmatch->nc_fsid, fsid);
	    }
	    else {
		match1 = dev_eq(lastmatch->nc_dev, fsid.fsid_dev);
	    }
	    if (allflag && match1)
		match1 = (thisdst == lastmatch->nc_ipaddr);
	}

	if (match1 == 0) {
	    /* wasn't the same as the last packet, try the hard way */
	    for (i = 0; i < nnfscounters; i++) {
		NFSCounter *nfscp = &nfs_counters[i];

		if (learnfs) {
		    match1 = fsid_eq(nfscp->nc_fsid, fsid);
		}
		else {
		    match1 = dev_eq(nfscp->nc_dev, fsid.fsid_dev);
		}

		/*
		 * Check server address.
		 */
		if (allflag && match1)
			match1 = (thisdst == nfscp->nc_ipaddr);

		if (match1) {
			nfscp->nc_proc[proc]++;
			nfscp->nc_interval++;
			nfscp->nc_total++;
			lastmatch = nfscp;
			break;
		}
	    }
	}
	else {
	    lastmatch->nc_proc[proc]++;
	    lastmatch->nc_interval++;
	    lastmatch->nc_total++;
	}

	/*
	 * We don't know about this file system, but we can
	 * learn.
	 */
	if (!match1 && learnfs && (nnfscounters < MAXEXPORT)) {
		static char fsname[64], prefix[64];
#if defined(SVR4) || defined(LINUX)
		sighold(SIGALRM);
#else
		int oldm;
		oldm = sigblock(sigmask(SIGALRM));
#endif
	    				/* no redisplay while unstable */

		i = nnfscounters++;

		nfs_counters[i].nc_fsid = fsid;
		nfs_counters[i].nc_dev = fsid.fsid_dev;
		nfs_counters[i].nc_proc[proc]++;
		nfs_counters[i].nc_interval++;
		nfs_counters[i].nc_total++;

		*prefix = 0;

		if (allflag) {
			struct hostent *hp;

			nfs_counters[i].nc_ipaddr = thisdst;
			hp = gethostbyaddr((char *) &thisdst, sizeof(thisdst),
					   AF_INET);

			if (hp) {
				char *index();
				char *dotp;

				sprintf(prefix, "%s", hp->h_name);

				if ((dotp = index(prefix, '.')) != NULL)
					*dotp = 0;
			}
			else {
				struct in_addr ia;
				ia.s_addr = thisdst;
				sprintf(prefix, "%s", inet_ntoa(ia));
			}
		}

		if (sfsname) {
		    /* file system ID is ASCII, not numeric, for server OS */
		    static char temp[17];

		    /* Make sure string is null-terminated */
		    strncpy(temp, sfsname, 16);
		    temp[16] = 0;
		    
		    sprintf(fsname, "%.8s:%s %s", prefix, temp, osname);
		}
		else {
		    sprintf(fsname, "%.12s(%ld,%ld)%s", prefix,
			nfs_counters[i].nc_dev.Major,
			nfs_counters[i].nc_dev.Minor,
			osname);
		}

		if (mapfile)
			nfs_counters[i].nc_name = strdup(map_fs_name(fsname));
		else
			nfs_counters[i].nc_name = strdup(fsname);

		sort_nfs_counters();
#if defined(SVR4) || defined(LINUX)
		sigrelse(SIGALRM);
#else
		(void) sigsetmask(oldm);	/* permit redisplay */
#endif
	}

	if (filelist == NULL)
		return;

	/* XXX could use a 1-behind cache here, too */

	/*
	 * Run through the file counters looking for the matching
	 * file.
	 */
	for (i = 0; i < nfilecounters; i++) {
	    FileCounter *fcp = &(fil_counters[i]);
	    
	    if (dev_eq(fcp->fc_dev, fsid.fsid_dev) && (fcp->fc_ino == ino)) {
		    fcp->fc_proc[proc]++;
		    fcp->fc_interval++;
		    fcp->fc_total++;
		    break;
	    }
	}
}

static void
nfs_count(fhandle_t *fh, int proc)
{
	my_fsid fsid;
	ino_t	ino;
	char *osname = "";
	char *sfsname = NULL;

	/* Convert file handle to our internal representations */
#ifdef	DEBUG
	Parse_fh((caddr_t *) fh, &fsid, &ino, &osname, &sfsname, to_self(thisdst));
#else
	Parse_fh((caddr_t *) fh, &fsid, &ino, NULL, &sfsname, to_self(thisdst));
#endif

	if (fhdebugf) {
	    /* Print file handles for specified servers */
	    unsigned char *fhcp = (unsigned char *)fh;
	    struct in_addr ia;
	    int i;

	    if (allflag) {	/* print host name if not implicit */
		ia.s_addr = thisdst;
		(void)printf("%s: ", inet_ntoa(ia));
	    }

	    for (i = 0; i < NFS_FHSIZE; i++)
		(void)printf("%x.", fhcp[i]);
	    (void)printf("\n");
	}

	do_nfs_count(fsid, ino, osname, sfsname, proc);
}

static void
nfs3_count(nfs_fh3 *fh, int proc)
{
  if (fh == NULL) {
    fprintf(stderr, "fh is NULL for proc %d\n", proc);
    return;
  }
  if (fh->fh3_length == 32)
    nfs_count(&fh->fh3_u.nfs_fh3_i.fh3_i, proc);
  else {
    unsigned char *fhp = fh->fh3_u.data;
    if (fhp[0] == 1 && fhp[1] == 0) {
      my_fsid fsid;
      ino_t ino;
      memset(&fsid, 0, sizeof(fsid));
      switch (fhp[2]) {
      case 0:
	/* 4 byte device id (ms-2-bytes major, ls-2-bytes minor),
	  4 byte inode number.  */
	fsid.fsid_dev.Major = (fhp[4] << 8) | fhp[5];
	fsid.fsid_dev.Minor = (fhp[6] << 8) | fhp[7];
	/* This is probably the inode number of the export point...
	  Might want to grab the actual file's inode number.  */
	ino = (fhp[8] << 24) | (fhp[9] << 16)
	    | (fhp[10] << 8) | fhp[11];
	do_nfs_count(fsid, ino, "Linux", NULL, proc);
	return;
      case 1:
	/* 4 byte filesystem ID.  */
	fsid.fsid_dev.Minor = ntohl((fhp[4] << 24) | (fhp[5] << 16)
				    | (fhp[6] << 8) | fhp[7]);
	do_nfs_count(fsid, 0, "Linux", NULL, proc);
	return;
      case 2:
	/* 4 byte device major, 4 byte device minor,
	  4 byte inode number.  */
	fsid.fsid_dev.Major =
	  (fhp[4] << 24) | (fhp[5] << 16)
	  | (fhp[6] << 8) | fhp[7];
	fsid.fsid_dev.Minor =
	  (fhp[8] << 24) | (fhp[9] << 16)
	  | (fhp[10] << 8) | fhp[11];
	/* This is probably the inode number of the export point...
	  Might want to grab the actual file's inode number.  */
	ino = (fhp[12] << 24) | (fhp[13] << 16)
	    | (fhp[14] << 8) | fhp[15];
	do_nfs_count(fsid, ino, "Linux", NULL, proc);
	return;
      case 3:
	/* 4 byte encoded device info, 4 byte inode number.  */
	fsid.fsid_dev.Major = ((fhp[5] & 0xf) << 8 ) | fhp[6];
	fsid.fsid_dev.Minor =
	  (fhp[4] << 12) | ((fhp[5] & 0xf0) << 8) | fhp[7];
	/* This is probably the inode number of the export point...
	  Might want to grab the actual file's inode number.  */
	ino = (fhp[8] << 24) | (fhp[9] << 16)
	    | (fhp[10] << 8) | fhp[11];
	do_nfs_count(fsid, ino, "Linux", NULL, proc);
	return;
      }
    }
    fprintf(stderr, "nfs3_count: length is %08x, proc is %d\n"
	    "%02x%02x%02x%02x\n"
	    "%02x%02x%02x%02x\n"
	    "%02x%02x%02x%02x\n", fh->fh3_length, proc,
	    fh->fh3_u.data[0], fh->fh3_u.data[1],
	    fh->fh3_u.data[2], fh->fh3_u.data[3],
	    fh->fh3_u.data[4], fh->fh3_u.data[5],
	    fh->fh3_u.data[6], fh->fh3_u.data[7],
	    fh->fh3_u.data[8], fh->fh3_u.data[9],
	    fh->fh3_u.data[10], fh->fh3_u.data[11]);
  }
}

/*
 * CountSrc uses a hash table to speed lookups.  Hash function
 *	uses high and low octect of IP address, so as to be
 *	fast and byte-order independent.  Table is organized
 *	as a array of linked lists.
 */
#define	HASHSIZE	0x100
#define	HASH(addr)	(((addr) & 0xFF) ^ (((addr) >> 24) & 0xFF))

ClientCounter *Addr_hashtable[HASHSIZE];	/* initially all NULL ptrs */

ClientCounter *cc_hint = clnt_counters;		/* one-element cache */

static void
CountSrc(ipaddrt src)
{
	register ClientCounter *ccp;
	int hcode = HASH(src);
	
	/* See if this is the same client as last time */
	if (cc_hint->cl_ipaddr == src) {
	    cc_hint->cl_total++;
	    cc_hint->cl_interval++;
	    return;
	}

	/* Search hash table */
	ccp = Addr_hashtable[hcode];
	while (ccp) {
	    if (ccp->cl_ipaddr == src) {
		ccp->cl_total++;
		ccp->cl_interval++;
		cc_hint = ccp;
		return;
	    }
	    ccp = ccp->cl_next;
	}
	
	/* new client */
	if (nclientcounters < MAXCLIENTS) {
	    struct hostent *hp;
	    static char clnt_name[64];
#if defined(SVR4) || defined(LINUX)
	    sighold(SIGALRM);
#else
	    int oldm;
	    oldm = sigblock(sigmask(SIGALRM));
#endif
	    				/* no redisplay while unstable */
	    
	    ccp = &(clnt_counters[nclientcounters]);
	    nclientcounters++;
	    
	    /* Add to hash table */
	    ccp->cl_next = Addr_hashtable[hcode];
	    Addr_hashtable[hcode] = ccp;

	    /* Fill in new ClientCounter */
	    ccp->cl_ipaddr = src;
	    hp = gethostbyaddr((char *) &ccp->cl_ipaddr,
			       sizeof(ccp->cl_ipaddr), AF_INET);
	    if (hp) {
		char *index();
		char *dotp;

		sprintf(clnt_name, "%s", hp->h_name);

		if ((dotp = index(clnt_name, '.')) != NULL)
			*dotp = 0;
	    }
	    else {
		struct in_addr ia;
		ia.s_addr = ccp->cl_ipaddr;
		sprintf(clnt_name, "%s", inet_ntoa(ia));
	    }

	    ccp->cl_name = strdup(clnt_name);
	    ccp->cl_total = 1;
	    ccp->cl_interval = 1;
	    sort_clnt_counters();
#if defined(SVR4) || defined(LINUX)
	    sigrelse(SIGALRM);
#else
	    (void) sigsetmask(oldm);	/* permit redisplay */
#endif
	}
}

/*
 * Must be called after sorting the clnt_counters[] table
 *	Should put busiest ones at front of list, but doesn't
 */
void
ClientHashRebuild(void)
{
	register int i;
	register ClientCounter *ccp;
	int hcode;

	bzero(Addr_hashtable, sizeof(Addr_hashtable));
	
	for (i = 0, ccp = clnt_counters; i < nclientcounters; i++, ccp++) {
	    hcode = HASH(ccp->cl_ipaddr);
	    ccp->cl_next = Addr_hashtable[hcode];
	    Addr_hashtable[hcode] = ccp;
	}
}

/*
 * Code to count authentication instances.
 */
#define UIDOFFSET	100000		/* add to a non-uid so it won't
					   conflict with uids		*/
static void
CountCallAuth(struct rpc_msg *msg)
{
	int i, len, auth;
	struct passwd *pw;
	unsigned int *oauth;
	static char user_name[MAXUSERNAMELEN + 1] =  {'\0'};

	auth = msg->ru.RM_cmb.cb_cred.oa_flavor;

	if (auth != AUTH_UNIX) {
		auth += UIDOFFSET;
	}
	else {
		/*
		 * Convert the opaque authorization into a uid.
		 * Should use the XDR decoders.
		 */
		oauth = (unsigned int *) msg->ru.RM_cmb.cb_cred.oa_base;
		len = ntohl(oauth[1]);

		if ((len % 4) != 0)
			len = len + 4 - len % 4;

		auth = ntohl(oauth[2 + len / 4]);
	}

	for (i=0; i < nauthcounters; i++) {
		if (auth_counters[i].ac_uid == auth) {
			auth_counters[i].ac_interval++;
			auth_counters[i].ac_total++;
			return;
		}
	}
	
	if (nauthcounters < MAXAUTHS) {
		nauthcounters++;

		auth_counters[i].ac_uid = auth;
		auth_counters[i].ac_total = 1;
		auth_counters[i].ac_interval = 1;

		switch (auth) {
		case AUTH_NULL + UIDOFFSET:
			auth_counters[i].ac_name = "AUTH_NULL";
			break;
		case AUTH_UNIX + UIDOFFSET:
			auth_counters[i].ac_name = "AUTH_UNIX";
			break;
		case AUTH_SHORT + UIDOFFSET:
			auth_counters[i].ac_name = "AUTH_SHORT";
			break;
		case AUTH_DES + UIDOFFSET:
			auth_counters[i].ac_name = "AUTH_DES";
			break;
		default:
			if ((pw = getpwuid(auth)) != NULL)
				strncpy(user_name, pw->pw_name, MAXUSERNAMELEN);
			else 
				snprintf(user_name, MAXUSERNAMELEN, "#%d", auth);
			auth_counters[i].ac_name = strdup(user_name);
			break;
		}

		sort_auth_counters();
	}
}

/*
 * Some code to look at response times.
 */
static void
nfs_hash_call(int proto, struct rpc_msg *msg, ipaddrt client, struct timeval *tstamp)
{
	int i;

	if (tstamp == NULL)
		return;

	for (i=0; i < NFSCALLHASHSIZE; i++) {
		if (nfs_calls[i].proto == 0) {
			nfs_calls[i].proto = proto;
			nfs_calls[i].proc = msg->rm_call.cb_proc;
			nfs_calls[i].client = client;
			nfs_calls[i].xid = msg->rm_xid;
			nfs_calls[i].time_sec = tstamp->tv_sec;
			nfs_calls[i].time_usec = tstamp->tv_usec;
			return;
		}
	}
}

static void
nfs_hash_reply(struct rpc_msg *msg, ipaddrt client, struct timeval *tstamp)
{
	int i;
	double diff;
	u_int proc;
	u_int32 xid = ntohl(msg->rm_xid);

	if (tstamp == NULL)
		return;

	for (i=0; i < NFSCALLHASHSIZE; i++) {
		if (nfs_calls[i].proto == 0)
			continue;

		if (nfs_calls[i].client == client &&
		    nfs_calls[i].xid == xid) {
			int proto = nfs_calls[i].proto;
			proc = prc_countmap[proto][nfs_calls[i].proc];

			diff = ((tstamp->tv_sec - nfs_calls[i].time_sec) *
				1000000 +
				tstamp->tv_usec - nfs_calls[i].time_usec) /
				1000.0;

			prc_counters[proto][proc].pr_complete++;
			prc_counters[proto][proc].pr_response += diff;
			prc_counters[proto][proc].pr_respsqr += diff * diff;

			if (diff > prc_counters[proto][proc].pr_maxresp)
				prc_counters[proto][proc].pr_maxresp = diff;

			nfs_calls[i].proto = 0;
			return;
		}
	}
}

/*
 * nfs3_filter - filter NFSv3 packets.
 */
static void
nfs3_filter(char *data, u_int length, ipaddrt src, struct timeval *tstamp)
{
	u_int proc;
	caddr_t args;
	SVCXPRT *xprt;
	struct rpc_msg msg;
	union nfs3_rfsargs nfs_rfsargs;
	char cred_area[2*MAX_AUTH_BYTES];

	msg.rm_call.cb_cred.oa_base = cred_area;
	msg.rm_call.cb_verf.oa_base = &(cred_area[MAX_AUTH_BYTES]);

	/*
	 * Act as if we received this packet through RPC.
	 */
	if (!udprpc_recv(data, length, &msg, &xprt))
		return;

	/*
	 * Get the NFS procedure number.
	 */
	proc = msg.rm_call.cb_proc;

	if (proc >= MAXNFS3PROC)
		return;

	CountCallAuth(&msg);
	nfs_hash_call(NFSv3, &msg, src, tstamp);

	/*
	 * Now decode the arguments to the procedure from
	 * XDR format.
	 */
	args = (caddr_t) &nfs_rfsargs;
	(void) bzero(args, nfs3_procs[proc].nfs_argsz);

	if (!SVC_GETARGS(xprt, nfs3_procs[proc].nfs_xdrargs, args))
		return;

	prc_counters[NFSv3][prc_countmap[NFSv3][proc]].pr_total++;
	prc_counters[NFSv3][prc_countmap[NFSv3][proc]].pr_interval++;

	CountSrc(src);

	/*
	 * Now count the packet in the appropriate file system's
	 * counters.
	 */
	switch (proc) {
	case NFSPROC3_NULL:
		break;
	case NFSPROC3_GETATTR:
		nfs3_count(&nfs_rfsargs.nfs_fh3_a, proc);
		break;
	case NFSPROC3_SETATTR:
		nfs3_count(&nfs_rfsargs.nfs_fh3_a, proc);
		break;
	case NFSPROC3_LOOKUP:
#if (defined(SUNOS5) && !defined(SUNOS58)) || defined(IRIX6)
		nfs3_count(&nfs_rfsargs.diropargs3_a.dir, proc);
#else
		nfs3_count(nfs_rfsargs.diropargs3_a.dirp, proc);
#endif
		break;
	case NFSPROC3_ACCESS:
		nfs3_count(&nfs_rfsargs.nfs_fh3_a, proc);
		break;
	case NFSPROC3_READLINK:
		nfs3_count(&nfs_rfsargs.nfs_fh3_a, proc);
		break;
	case NFSPROC3_READ:
		nfs3_count(&nfs_rfsargs.nfs_fh3_a, proc);
		break;
	case NFSPROC3_WRITE:
		nfs3_count(&nfs_rfsargs.nfs_fh3_a, proc);
		break;
	case NFSPROC3_CREATE:
#if (defined(SUNOS5) && !defined(SUNOS58)) || defined(IRIX6)
		nfs3_count(&nfs_rfsargs.diropargs3_a.dir, proc);
#else
		nfs3_count(nfs_rfsargs.diropargs3_a.dirp, proc);
#endif
		break;
	case NFSPROC3_MKDIR:
#if (defined(SUNOS5) && !defined(SUNOS58)) || defined(IRIX6)
		nfs3_count(&nfs_rfsargs.diropargs3_a.dir, proc);
#else
		nfs3_count(nfs_rfsargs.diropargs3_a.dirp, proc);
#endif
		break;
	case NFSPROC3_SYMLINK:
#if (defined(SUNOS5) && !defined(SUNOS58)) || defined(IRIX6)
		nfs3_count(&nfs_rfsargs.diropargs3_a.dir, proc);
#else
		nfs3_count(nfs_rfsargs.diropargs3_a.dirp, proc);
#endif
		break;
	case NFSPROC3_MKNOD:
#if (defined(SUNOS5) && !defined(SUNOS58)) || defined(IRIX6)
		nfs3_count(&nfs_rfsargs.diropargs3_a.dir, proc);
#else
		nfs3_count(nfs_rfsargs.diropargs3_a.dirp, proc);
#endif
		break;
	case NFSPROC3_REMOVE:
#if (defined(SUNOS5) && !defined(SUNOS58)) || defined(IRIX6)
		nfs3_count(&nfs_rfsargs.diropargs3_a.dir, proc);
#else
		nfs3_count(nfs_rfsargs.diropargs3_a.dirp, proc);
#endif
		break;
	case NFSPROC3_RMDIR:
#if (defined(SUNOS5) && !defined(SUNOS58)) || defined(IRIX6)
		nfs3_count(&nfs_rfsargs.diropargs3_a.dir, proc);
#else
		nfs3_count(nfs_rfsargs.diropargs3_a.dirp, proc);
#endif
		break;
	case NFSPROC3_RENAME:
#if (defined(SUNOS5) && !defined(SUNOS58)) || defined(IRIX6)
		nfs3_count(&nfs_rfsargs.RENAME3args_a.from.dir, proc);
#else
		nfs3_count(nfs_rfsargs.RENAME3args_a.from.dirp, proc);
#endif
		break;
	case NFSPROC3_LINK:
		nfs3_count(&nfs_rfsargs.LINK3args_a.file, proc);
		break;
	case NFSPROC3_READDIR:
		nfs3_count(&nfs_rfsargs.nfs_fh3_a, proc);
		break;
	case NFSPROC3_READDIRPLUS:
		nfs3_count(&nfs_rfsargs.nfs_fh3_a, proc);
		break;
	case NFSPROC3_FSSTAT:
		nfs3_count(&nfs_rfsargs.nfs_fh3_a, proc);
		break;
	case NFSPROC3_FSINFO:
		nfs3_count(&nfs_rfsargs.nfs_fh3_a, proc);
		break;
	case NFSPROC3_PATHCONF:
		nfs3_count(&nfs_rfsargs.nfs_fh3_a, proc);
		break;
	case NFSPROC3_COMMIT:
		nfs3_count(&nfs_rfsargs.nfs_fh3_a, proc);
		break;
	}

	/*
	 * Decide whether it's a read or write process.
	 */
	switch (nfs3_procs[proc].nfs_proctype) {
	case NFS_READ:
		pkt_counters[PKT_NFS3READ].pc_interval++;
		pkt_counters[PKT_NFS3READ].pc_total++;
		break;
	case NFS_WRITE:
		pkt_counters[PKT_NFS3WRITE].pc_interval++;
		pkt_counters[PKT_NFS3WRITE].pc_total++;
		break;
	}
}

void
get_rpc_ports(void)
{
  struct sockaddr_in sai;
  int i;
  if (serverflag)
    return;
  memset(&sai, 0, sizeof(sai));
  sai.sin_family = AF_INET;
  for (i = 0; (dstaddrs[i] != 0) && (i < MAXHOSTADDR); i++) {
    struct pmaplist *pml;
    sai.sin_addr.s_addr = dstaddrs[0];
    pml = pmap_getmaps(&sai);
    while (pml != NULL) {
      struct pmaplist *cur = pml;
      pml = pml->pml_next;
      if (cur->pml_map.pm_prot == IPPROTO_UDP
	  && cur->pml_map.pm_port != NFS_PORT) {
	int found = 0;
	unsigned int j;
	for (j = 0; j < rpcPortCnt; j++)
	  if (rpcPort[j] == cur->pml_map.pm_port) {
	    found = 1;
	    break;
	  }
	if (!found) {
	  if ((rpcPort = realloc(rpcPort, (rpcPortCnt + 1)
				 * sizeof(unsigned long))) == NULL) {
	    perror("get_rpc_ports: realloc");
	    rpcPortCnt = 0;
	    return;
	  }
	  rpcPort[rpcPortCnt++] = cur->pml_map.pm_port;
	}
      }
      free(cur);
    }
  }
}
