/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <sys/errno.h>
#include <sys/tiuser.h>
#include <setjmp.h>
#include <pwd.h>
#include <grp.h>

#include <rpc/types.h>
#include <rpc/xdr.h>
#include <rpc/auth.h>
#include <rpc/clnt.h>
#include <rpc/rpc_msg.h>
#include <string.h>
#include "snoop.h"

#include <sys/stat.h>

extern char *get_sum_line();
extern void check_retransmit();
extern char *sum_nfsfh();
extern int sum_nfsstat();
extern int detail_nfsstat();
extern void detail_nfsfh();
extern void detail_fattr();
extern void skip_fattr();
extern char *sum_nfsfh3();
extern int sum_nfsstat3();
extern int detail_nfsstat3();
extern void detail_post_op_attr();
extern void detail_nfsfh3();
extern int sum_nfsstat4();
extern int detail_nfsstat4();

extern jmp_buf xdr_err;

static void aclcall2();
static void aclreply2();
static void aclcall3();
static void aclreply3();
static void aclcall4();
static void aclreply4();
static void detail_access2();
static char *sum_access2();
static void detail_mask();
static void detail_secattr();
static void detail_aclent();
static char *detail_uname();
static char *detail_gname();
static char *detail_perm(ushort_t);
static void interpret_nfs_acl2(int, int, int, int, int, char *, int);
static void interpret_nfs_acl3(int, int, int, int, int, char *, int);
static void interpret_nfs_acl4(int, int, int, int, int, char *, int);

#define	ACLPROC2_NULL		((unsigned long)(0))
#define	ACLPROC2_GETACL		((unsigned long)(1))
#define	ACLPROC2_SETACL		((unsigned long)(2))
#define	ACLPROC2_GETATTR	((unsigned long)(3))
#define	ACLPROC2_ACCESS		((unsigned long)(4))
#define	ACLPROC2_GETXATTRDIR	((unsigned long)(5))

#define	ACLPROC3_NULL		((unsigned long)(0))
#define	ACLPROC3_GETACL		((unsigned long)(1))
#define	ACLPROC3_SETACL		((unsigned long)(2))
#define	ACLPROC3_GETXATTRDIR	((unsigned long)(3))

#define	ACLPROC4_NULL		((unsigned long)(0))
#define	ACLPROC4_GETACL		((unsigned long)(1))
#define	ACLPROC4_SETACL		((unsigned long)(2))

#define	NA_USER_OBJ	0x1
#define	NA_USER		0x2
#define	NA_GROUP_OBJ	0x4
#define	NA_GROUP	0x8
#define	NA_CLASS_OBJ	0x10
#define	NA_OTHER_OBJ	0x20
#define	NA_ACL_DEFAULT	0x1000

#define	NA_DEF_USER_OBJ		(NA_USER_OBJ | NA_ACL_DEFAULT)
#define	NA_DEF_USER		(NA_USER | NA_ACL_DEFAULT)
#define	NA_DEF_GROUP_OBJ	(NA_GROUP_OBJ | NA_ACL_DEFAULT)
#define	NA_DEF_GROUP		(NA_GROUP | NA_ACL_DEFAULT)
#define	NA_DEF_CLASS_OBJ	(NA_CLASS_OBJ | NA_ACL_DEFAULT)
#define	NA_DEF_OTHER_OBJ	(NA_OTHER_OBJ | NA_ACL_DEFAULT)

#define	NA_ACL		0x1
#define	NA_ACLCNT	0x2
#define	NA_DFACL	0x4
#define	NA_DFACLCNT	0x8

#define	ACCESS2_READ	0x0001
#define	ACCESS2_LOOKUP	0x0002
#define	ACCESS2_MODIFY	0x0004
#define	ACCESS2_EXTEND	0x0008
#define	ACCESS2_DELETE	0x0010
#define	ACCESS2_EXECUTE	0x0020

static char *procnames_short_v2[] = {
	"NULL2",	/*  0 */
	"GETACL2",	/*  1 */
	"SETACL2",	/*  2 */
	"GETATTR2",	/*  3 */
	"ACCESS2",	/*  4 */
	"GETXATTRDIR2",	/*  5 */
};
static char *procnames_short_v3[] = {
	"NULL3",	/*  0 */
	"GETACL3",	/*  1 */
	"SETACL3",	/*  2 */
	"GETXATTRDIR3",	/*  3 */
};
static char *procnames_short_v4[] = {
	"NULL4",	/*  0 */
	"GETACL4",	/*  1 */
	"SETACL4",	/*  2 */
};

static char *procnames_long_v2[] = {
	"Null procedure",			/*  0 */
	"Get file access control list",		/*  1 */
	"Set file access control list",		/*  2 */
	"Get file attributes",			/*  3 */
	"Check access permission",		/*  4 */
	"Get extended attribute directory",	/*  5 */
};
static char *procnames_long_v3[] = {
	"Null procedure",			/*  0 */
	"Get file access control list",		/*  1 */
	"Set file access control list",		/*  2 */
	"Get extended attribute directory",	/*  3 */
};
static char *procnames_long_v4[] = {
	"Null procedure",			/*  0 */
	"Get file access control list",		/*  1 */
	"Set file access control list",		/*  2 */
};

#define	MAXPROC_V2	5
#define	MAXPROC_V3	3
#define	MAXPROC_V4	2

/* ARGSUSED */
void
interpret_nfs_acl(flags, type, xid, vers, proc, data, len)
	int flags, type, xid, vers, proc;
	char *data;
	int len;
{

	if (vers == 2) {
		interpret_nfs_acl2(flags, type, xid, vers, proc, data, len);
		return;
	}

	if (vers == 3) {
		interpret_nfs_acl3(flags, type, xid, vers, proc, data, len);
		return;
	}

	if (vers == 4) {
		interpret_nfs_acl4(flags, type, xid, vers, proc, data, len);
		return;
	}
}

static void
interpret_nfs_acl2(int flags, int type, int xid, int vers, int proc,
    char *data, int len)
{
	char *line;
	char buff[2048];
	int off, sz;
	char *fh;
	ulong_t mask;

	if (proc < 0 || proc > MAXPROC_V2)
		return;

	if (flags & F_SUM) {
		line = get_sum_line();

		if (type == CALL) {
			(void) sprintf(line, "NFS_ACL C %s",
			    procnames_short_v2[proc]);
			line += strlen(line);
			switch (proc) {
			case ACLPROC2_GETACL:
				fh = sum_nfsfh();
				mask = getxdr_u_long();
				(void) sprintf(line, "%s mask=%lu", fh, mask);
				break;
			case ACLPROC2_SETACL:
				(void) sprintf(line, sum_nfsfh());
				break;
			case ACLPROC2_GETATTR:
				(void) sprintf(line, sum_nfsfh());
				break;
			case ACLPROC2_ACCESS:
				fh = sum_nfsfh();
				(void) sprintf(line, "%s (%s)", fh,
				    sum_access2());
				break;
			case ACLPROC2_GETXATTRDIR:
				fh = sum_nfsfh();
				(void) sprintf(line, "%s create=%s", fh,
				    getxdr_bool() ? "true" : "false");
				break;
			default:
				break;
			}

			check_retransmit(line, (ulong_t)xid);
		} else {
			(void) sprintf(line, "NFS_ACL R %s ",
			    procnames_short_v2[proc]);
			line += strlen(line);
			switch (proc) {
			case ACLPROC2_GETACL:
				(void) sum_nfsstat(line);
				break;
			case ACLPROC2_SETACL:
				(void) sum_nfsstat(line);
				break;
			case ACLPROC2_GETATTR:
				(void) sum_nfsstat(line);
				break;
			case ACLPROC2_ACCESS:
				if (sum_nfsstat(line) == 0) {
					skip_fattr();
					line += strlen(line);
					(void) sprintf(line, " (%s)",
					    sum_access2());
				}
				break;
			case ACLPROC2_GETXATTRDIR:
				if (sum_nfsstat(line) == 0) {
					line += strlen(line);
					(void) sprintf(line, sum_nfsfh());
				}
				break;
			default:
				break;
			}
		}
	}

	if (flags & F_DTAIL) {
		show_header("NFS_ACL:  ", "Sun NFS_ACL", len);
		show_space();
		(void) sprintf(get_line(0, 0), "Proc = %d (%s)",
		    proc, procnames_long_v2[proc]);
		if (type == CALL)
			aclcall2(proc);
		else
			aclreply2(proc);
		show_trailer();
	}
}

static void
interpret_nfs_acl3(int flags, int type, int xid, int vers, int proc,
    char *data, int len)
{
	char *line;
	char buff[2048];
	int off, sz;
	char *fh;
	ulong_t mask;

	if (proc < 0 || proc > MAXPROC_V3)
		return;

	if (flags & F_SUM) {
		line = get_sum_line();

		if (type == CALL) {
			(void) sprintf(line, "NFS_ACL C %s",
			    procnames_short_v3[proc]);
			line += strlen(line);
			switch (proc) {
			case ACLPROC3_GETACL:
				fh = sum_nfsfh3();
				mask = getxdr_u_long();
				(void) sprintf(line, "%s mask=%lu", fh, mask);
				break;
			case ACLPROC3_SETACL:
				(void) sprintf(line, sum_nfsfh3());
				break;
			case ACLPROC3_GETXATTRDIR:
				fh = sum_nfsfh3();
				(void) sprintf(line, "%s create=%s", fh,
				    getxdr_bool() ? "true" : "false");
				break;
			default:
				break;
			}

			check_retransmit(line, (ulong_t)xid);
		} else {
			(void) sprintf(line, "NFS_ACL R %s ",
			    procnames_short_v3[proc]);
			line += strlen(line);
			switch (proc) {
			case ACLPROC3_GETACL:
				(void) sum_nfsstat3(line);
				break;
			case ACLPROC3_SETACL:
				(void) sum_nfsstat3(line);
				break;
			case ACLPROC3_GETXATTRDIR:
				if (sum_nfsstat3(line) == 0) {
					line += strlen(line);
					(void) sprintf(line, sum_nfsfh3());
				}
				break;
			default:
				break;
			}
		}
	}

	if (flags & F_DTAIL) {
		show_header("NFS_ACL:  ", "Sun NFS_ACL", len);
		show_space();
		(void) sprintf(get_line(0, 0), "Proc = %d (%s)",
		    proc, procnames_long_v3[proc]);
		if (type == CALL)
			aclcall3(proc);
		else
			aclreply3(proc);
		show_trailer();
	}
}

static void
interpret_nfs_acl4(int flags, int type, int xid, int vers, int proc,
    char *data, int len)
{
	char *line;
	char buff[2048];
	int off, sz;
	char *fh;
	ulong_t mask;

	if (proc < 0 || proc > MAXPROC_V4)
		return;

	if (flags & F_SUM) {
		line = get_sum_line();

		if (type == CALL) {
			(void) sprintf(line, "NFS_ACL C %s",
			    procnames_short_v4[proc]);
			line += strlen(line);
			switch (proc) {
			case ACLPROC4_GETACL:
				fh = sum_nfsfh3();
				mask = getxdr_u_long();
				(void) sprintf(line, "%s mask=%lu", fh, mask);
				break;
			case ACLPROC4_SETACL:
				(void) sprintf(line, sum_nfsfh3());
				break;
			default:
				break;
			}

			check_retransmit(line, (ulong_t)xid);
		} else {
			(void) sprintf(line, "NFS_ACL R %s ",
			    procnames_short_v4[proc]);
			line += strlen(line);
			switch (proc) {
			case ACLPROC4_GETACL:
				(void) sum_nfsstat4(line);
				break;
			case ACLPROC4_SETACL:
				(void) sum_nfsstat4(line);
				break;
			default:
				break;
			}
		}
	}

	if (flags & F_DTAIL) {
		show_header("NFS_ACL:  ", "Sun NFS_ACL", len);
		show_space();
		(void) sprintf(get_line(0, 0), "Proc = %d (%s)",
		    proc, procnames_long_v4[proc]);
		if (type == CALL)
			aclcall4(proc);
		else
			aclreply4(proc);
		show_trailer();
	}
}

int
sum_nfsstat4(char *line)
{
	ulong_t status;
	char *p, *nfsstat4_to_name(int);

	status = getxdr_long();
	p = nfsstat4_to_name(status);
	(void) strcpy(line, p);
	return (status);
}

int
detail_nfsstat4()
{
	ulong_t status;
	char buff[64];
	int pos;

	pos = getxdr_pos();
	status = sum_nfsstat4(buff);

	(void) sprintf(get_line(pos, getxdr_pos()), "Status = %d (%s)",
	    status, buff);

	return ((int)status);
}

/*
 * Print out version 2 NFS_ACL call packets
 */
static void
aclcall2(proc)
	int proc;
{

	switch (proc) {
	case ACLPROC2_GETACL:
		detail_nfsfh();
		detail_mask();
		break;
	case ACLPROC2_SETACL:
		detail_nfsfh();
		detail_secattr();
		break;
	case ACLPROC2_GETATTR:
		detail_nfsfh();
		break;
	case ACLPROC2_ACCESS:
		detail_nfsfh();
		detail_access2();
		break;
	default:
		break;
	}
}

/*
 * Print out version 2 NFS_ACL reply packets
 */
static void
aclreply2(proc)
	int proc;
{

	switch (proc) {
	case ACLPROC2_GETACL:
		if (detail_nfsstat() == 0) {
			detail_fattr();
			detail_secattr();
		}
		break;
	case ACLPROC2_SETACL:
		if (detail_nfsstat() == 0)
			detail_fattr();
		break;
	case ACLPROC2_GETATTR:
		if (detail_nfsstat() == 0)
			detail_fattr();
		break;
	case ACLPROC2_ACCESS:
		if (detail_nfsstat() == 0) {
			detail_fattr();
			detail_access2();
		}
		break;
	default:
		break;
	}
}

/*
 * Print out version 3 NFS_ACL call packets
 */
static void
aclcall3(proc)
	int proc;
{

	switch (proc) {
	case ACLPROC3_GETACL:
		detail_nfsfh3();
		detail_mask();
		break;
	case ACLPROC3_SETACL:
		detail_nfsfh3();
		detail_secattr();
		break;
	default:
		break;
	}
}

/*
 * Print out version 3 NFS_ACL reply packets
 */
static void
aclreply3(proc)
	int proc;
{

	switch (proc) {
	case ACLPROC3_GETACL:
		if (detail_nfsstat3() == 0) {
			detail_post_op_attr("");
			detail_secattr();
		}
		break;
	case ACLPROC3_SETACL:
		if (detail_nfsstat3() == 0)
			detail_post_op_attr("");
		break;
	default:
		break;
	}
}

/*
 * Print out version 4 NFS_ACL call packets
 */
static void
aclcall4(proc)
	int proc;
{

	switch (proc) {
	case ACLPROC4_GETACL:
		detail_nfsfh3();
		detail_mask();
		break;
	case ACLPROC4_SETACL:
		detail_nfsfh3();
		detail_secattr();
		break;
	default:
		break;
	}
}

/*
 * Print out version 4 NFS_ACL reply packets
 */
static void
aclreply4(proc)
	int proc;
{

	switch (proc) {
	case ACLPROC4_GETACL:
		if (detail_nfsstat4() == 0) {
			detail_post_op_attr("");
			detail_secattr();
		}
		break;
	case ACLPROC4_SETACL:
		if (detail_nfsstat4() == 0)
			detail_post_op_attr("");
		break;
	default:
		break;
	}
}

static void
detail_access2()
{
	uint_t bits;

	bits = showxdr_u_long("Access bits = 0x%08x");
	(void) sprintf(get_line(0, 0), "	%s",
	    getflag(bits, ACCESS2_READ, "Read", "(no read)"));
	(void) sprintf(get_line(0, 0), "	%s",
	    getflag(bits, ACCESS2_LOOKUP, "Lookup", "(no lookup)"));
	(void) sprintf(get_line(0, 0), "	%s",
	    getflag(bits, ACCESS2_MODIFY, "Modify", "(no modify)"));
	(void) sprintf(get_line(0, 0), "	%s",
	    getflag(bits, ACCESS2_EXTEND, "Extend", "(no extend)"));
	(void) sprintf(get_line(0, 0), "	%s",
	    getflag(bits, ACCESS2_DELETE, "Delete", "(no delete)"));
	(void) sprintf(get_line(0, 0), "	%s",
	    getflag(bits, ACCESS2_EXECUTE, "Execute", "(no execute)"));
}

static char *
sum_access2()
{
	int bits;
	static char buff[22];

	bits = getxdr_u_long();
	buff[0] = '\0';

	if (bits & ACCESS2_READ)
		(void) strcat(buff, "read,");
	if (bits & ACCESS2_LOOKUP)
		(void) strcat(buff, "lookup,");
	if (bits & ACCESS2_MODIFY)
		(void) strcat(buff, "modify,");
	if (bits & ACCESS2_EXTEND)
		(void) strcat(buff, "extend,");
	if (bits & ACCESS2_DELETE)
		(void) strcat(buff, "delete,");
	if (bits & ACCESS2_EXECUTE)
		(void) strcat(buff, "execute,");
	if (buff[0] != '\0')
		buff[strlen(buff) - 1] = '\0';

	return (buff);
}

static void
detail_mask()
{
	ulong_t mask;

	mask = showxdr_u_long("Mask = 0x%lx");
	(void) sprintf(get_line(0, 0), "	%s",
	    getflag(mask, NA_ACL, "aclent", "(no aclent)"));
	(void) sprintf(get_line(0, 0), "	%s",
	    getflag(mask, NA_ACLCNT, "aclcnt", "(no aclcnt)"));
	(void) sprintf(get_line(0, 0), "	%s",
	    getflag(mask, NA_DFACL, "dfaclent", "(no dfaclent)"));
	(void) sprintf(get_line(0, 0), "	%s",
	    getflag(mask, NA_DFACLCNT, "dfaclcnt", "(no dfaclcnt)"));
}

static void
detail_secattr()
{

	detail_mask();
	showxdr_long("Aclcnt = %d");
	detail_aclent();
	showxdr_long("Dfaclcnt = %d");
	detail_aclent();
}

static void
detail_aclent()
{
	int count;
	int type;
	int id;
	ushort_t perm;

	count = getxdr_long();
	while (count-- > 0) {
		type = getxdr_long();
		id = getxdr_long();
		perm = getxdr_u_short();
		switch (type) {
		case NA_USER:
			(void) sprintf(get_line(0, 0), "\tuser:%s:%s",
			    detail_uname(id), detail_perm(perm));
			break;
		case NA_USER_OBJ:
			(void) sprintf(get_line(0, 0), "\tuser::%s",
			    detail_perm(perm));
			break;
		case NA_GROUP:
			(void) sprintf(get_line(0, 0), "\tgroup:%s:%s",
			    detail_gname(id), detail_perm(perm));
			break;
		case NA_GROUP_OBJ:
			(void) sprintf(get_line(0, 0), "\tgroup::%s",
			    detail_perm(perm));
			break;
		case NA_CLASS_OBJ:
			(void) sprintf(get_line(0, 0), "\tmask:%s",
			    detail_perm(perm));
			break;
		case NA_OTHER_OBJ:
			(void) sprintf(get_line(0, 0), "\tother:%s",
			    detail_perm(perm));
			break;
		case NA_DEF_USER:
			(void) sprintf(get_line(0, 0), "\tdefault:user:%s:%s",
			    detail_uname(id), detail_perm(perm));
			break;
		case NA_DEF_USER_OBJ:
			(void) sprintf(get_line(0, 0), "\tdefault:user::%s",
			    detail_perm(perm));
			break;
		case NA_DEF_GROUP:
			(void) sprintf(get_line(0, 0), "\tdefault:group:%s:%s",
			    detail_gname(id), detail_perm(perm));
			break;
		case NA_DEF_GROUP_OBJ:
			(void) sprintf(get_line(0, 0), "\tdefault:group::%s",
			    detail_perm(perm));
			break;
		case NA_DEF_CLASS_OBJ:
			(void) sprintf(get_line(0, 0), "\tdefault:mask:%s",
			    detail_perm(perm));
			break;
		case NA_DEF_OTHER_OBJ:
			(void) sprintf(get_line(0, 0), "\tdefault:other:%s",
			    detail_perm(perm));
			break;
		default:
			(void) sprintf(get_line(0, 0), "\tunrecognized entry");
			break;
		}
	}
}

static char *
detail_uname(uid_t uid)
{
	struct passwd *pwd;
	static char uidp[10];

	pwd = getpwuid(uid);
	if (pwd == NULL) {
		sprintf(uidp, "%d", uid);
		return (uidp);
	}
	return (pwd->pw_name);
}

static char *
detail_gname(gid_t gid)
{
	struct group *grp;
	static char gidp[10];

	grp = getgrgid(gid);
	if (grp == NULL) {
		sprintf(gidp, "%d", gid);
		return (gidp);
	}
	return (grp->gr_name);
}

static char *perms[] = {
	"---",
	"--x",
	"-w-",
	"-wx",
	"r--",
	"r-x",
	"rw-",
	"rwx"
};
static char *
detail_perm(ushort_t perm)
{

	if (perm >= sizeof (perms) / sizeof (perms[0]))
		return ("?");
	return (perms[perm]);
}