/*
 * dispatch.c	This file contains the function dispatch table.
 *
 * Authors:	Donald J. Becker, <becker@super.org>
 *		Rick Sladkey, <jrs@world.std.com>
 *		Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
 *
 *		This software maybe be used for any purpose provided
 *		the above copyright notice is retained.  It is supplied
 *		as is, with no warranty expressed or implied.
 */

#include "nfsd.h"

union argument_types 	argument;
union result_types	result;
clnt_param		*cp;

extern int
    nfsd_nfsproc_null_2(), nfsd_nfsproc_getattr_2(), nfsd_nfsproc_setattr_2(),
    nfsd_nfsproc_root_2(), nfsd_nfsproc_lookup_2(), nfsd_nfsproc_readlink_2(),
    nfsd_nfsproc_read_2(), nfsd_nfsproc_writecache_2(), nfsd_nfsproc_write_2(),
    nfsd_nfsproc_create_2(), nfsd_nfsproc_remove_2(), nfsd_nfsproc_rename_2(),
    nfsd_nfsproc_link_2(), nfsd_nfsproc_symlink_2(), nfsd_nfsproc_mkdir_2(),
    nfsd_nfsproc_rmdir_2(), nfsd_nfsproc_readdir_2(), nfsd_nfsproc_statfs_2();

/* Allow the following to be over-ridden at compile time. */

#ifndef ROOT_UID
#define ROOT_UID	0		/* Root user's ID. */
#endif

#ifndef NOBODY_UID
#define NOBODY_UID	((uid_t) 65534)	/* The unprivileged user. */
#endif

#ifndef NOBODY_GID
#define NOBODY_GID	((gid_t) 65534)	/* The unprivileged group. */
#endif

uid_t eff_uid = 0;			/* Current effective IDs. */
gid_t eff_gid = 0;
GETGROUPS_T last_gids[NGRPS];		/* Current supplementary gids. */
int last_len = -1;

/*
 * This is a dispatch table to simplify error checking,
 * and supply return attributes for NFS functions.
 */

#ifdef __STDC__
#define CONCAT(a,b)	a##b
#define CONCAT3(a,b,c)	a##b##c
#define STRING(a)	#a
#else
#define CONCAT(a,b)	a/**/b
#define CONCAT3(a,b,c)	a/**/b/**/c
#define STRING(a)	"a"
#endif

#define table_ent(auth, ro, cred, res_type, arg_type, funct) {	\
	auth, ro, cred, sizeof(res_type), sizeof(arg_type),	\
	CONCAT(xdr_,res_type), CONCAT(xdr_,arg_type),		\
	CONCAT3(nfsd_nfsproc_,funct,_2), STRING(funct),		\
	CONCAT(pr_,arg_type)					\
}

#define nil	char
#define xdr_nil	xdr_void
#define pr_nil	pr_void
#define pr_char	pr_void

struct dispatch_entry {
	int	authenticate;		/* zero if op perm. to any	*/
	int	read_only;		/* zero if op perm. on RO FS	*/
	int	credentials;		/* zero if no creditials needed	*/
	int	res_size, arg_size;	/* sizeof the res/arg structs	*/
	bool_t	(*xdr_result)();
	bool_t	(*xdr_argument)();
	int	(*funct)();		/* function handler		*/
	char	*name;			/* name of function		*/
	char	*(*log_print)();	/* ptr to debug handler		*/
};

static struct dispatch_entry dtable[] = {
	table_ent(0,0,0,nil,nil,null),
	table_ent(1,0,1,attrstat,nfs_fh,getattr),
	table_ent(1,1,1,attrstat,sattrargs,setattr),
	table_ent(0,0,0,nil,nil,root),
	table_ent(1,0,1,diropres,diropargs,lookup),
	table_ent(1,0,1,readlinkres,nfs_fh,readlink),
	table_ent(1,0,1,readres,readargs,read),
	table_ent(0,0,0,nil,nil,writecache),
	table_ent(1,1,1,attrstat,writeargs,write),
	table_ent(1,1,1,diropres,createargs,create),
	table_ent(1,1,1,nfsstat,diropargs,remove),
	table_ent(1,1,1,nfsstat,renameargs,rename),
	table_ent(1,1,1,nfsstat,linkargs,link),
	table_ent(1,1,1,nfsstat,symlinkargs,symlink),
	table_ent(1,1,1,diropres,createargs,mkdir),
	table_ent(1,1,1,nfsstat,diropargs,rmdir),
	table_ent(1,0,1,readdirres,readdirargs,readdir),
	table_ent(1,0,0,statfsres,nfs_fh,statfs),
};

static _PRO( void set_ids, (struct svc_req *rqstp) );

void nfs_dispatch(rqstp, transp)
struct svc_req *rqstp;
SVCXPRT *transp;
{
	unsigned int proc_index = rqstp->rq_proc;
	struct dispatch_entry *dent;

	if (proc_index >= (sizeof(dtable) / sizeof(dtable[0]))) {
		svcerr_noproc(transp);
		return;
	}
	dent = &dtable[proc_index];

	memset(&argument, 0, dent->arg_size);
	if (!svc_getargs(transp, dent->xdr_argument, &argument)) {
		svcerr_decode(transp);
		return;
	}
	/* Clear the result structure. */
	memset(&result, 0, dent->res_size);

	/* Log the call. */
	log_call(rqstp, dent->name, dent->log_print(&argument));

	/* Initialize our variables for determining the attributes of
	   the file system in nfsd.c */
	svc_rqstp = rqstp;
	client_authenticate = dent->authenticate;

#ifdef READ_ONLY
	/* Punt a write-type request if we are READ_ONLY. */
	if (dent->read_only) {
		result.nfsstat = NFSERR_ROFS;
		goto report_status;
	}
#endif

	/* Establish the credentials and set the effective user IDs. */
	if (dent->credentials)
		set_ids(rqstp);

	/* Do the function call itself. */
	result.nfsstat = (*dent->funct) (&argument);

#ifdef READ_ONLY
report_status:
#endif
	dprintf(1, "result: %d\n", result.nfsstat);

	if (/* result.nfsstat >= 0 && */ /* nfsstat is unsigned? */
	  !svc_sendreply(transp, dent->xdr_result, (caddr_t) & result)) {
		svcerr_systemerr(transp);
	}
	if (!svc_freeargs(transp, dent->xdr_argument, &argument)) {
		dprintf(0, "unable to free RPC arguments, exiting\n");
		exit(1);
	}
}

static void set_ids(rqstp)
struct svc_req *rqstp;
{
	uid_t cred_uid;
	gid_t cred_gid;
	int cred_len;
	GETGROUPS_T *cred_gids;
	GETGROUPS_T fake_gid;

	if (rqstp->rq_cred.oa_flavor == AUTH_UNIX) {
		struct authunix_parms *unix_cred;

		unix_cred = (struct authunix_parms *) rqstp->rq_clntcred;
		cred_uid = unix_cred->aup_uid;
		cred_gid = unix_cred->aup_gid;
		cred_len = unix_cred->aup_len;
		cred_gids = unix_cred->aup_gids;
	} else {
		cred_uid = NOBODY_UID;
		cred_gid = NOBODY_GID;
		/* Construct a list of one gid. */
		cred_len = 1;
		cred_gids = &fake_gid;
		fake_gid = cred_gid;
	}

	/* To set any IDs we first need to be root. What a pain. */

	/* First set the group ID. */
	if (eff_gid != cred_gid) {
		if (eff_uid != ROOT_UID) {
			if (seteuid(ROOT_UID) < 0)
				dprintf(0, "Unable to seteuid %d: %s\n",
				    ROOT_UID, strerror(errno));
			else
				eff_uid = ROOT_UID;
		}
		if (setegid(cred_gid) < 0)
			dprintf(0, "Unable to setegid %d: %s\n",
			    cred_gid, strerror(errno));
		else
			eff_gid = cred_gid;
	}

#ifdef HAVE_SETGROUPS
	/* Next set the supplementary group IDs if possible. */
	if (cred_len < 0 || cred_len > NGRPS)
		dprintf(0, "Negative or huge cred_len: %d\n", cred_len);
	else if (cred_len != last_len
	    || memcmp(cred_gids, last_gids, last_len*sizeof(gid_t))) {
		if (eff_uid != ROOT_UID) {
			if (seteuid(ROOT_UID) < 0)
				dprintf(0, "Unable to seteuid %d: %s\n",
				    ROOT_UID, strerror(errno));
			else
				eff_uid = ROOT_UID;
		}
		if (setgroups(cred_len, cred_gids) < 0)
			dprintf(0, "Unable to setgroups: %s\n",
			    strerror(errno));
		else {
			memcpy(last_gids, cred_gids, cred_len*sizeof(gid_t));
			last_len = cred_len;
		}
	}
#endif /* HAVE_SETGROUPS */

	/* Finally, set the user ID. */
	if (eff_uid != cred_uid) {
		if (eff_uid != ROOT_UID && seteuid(ROOT_UID) < 0)
			dprintf(0, "Unable to seteuid %d: %s\n",
			    ROOT_UID, strerror(errno));
		if (seteuid(cred_uid) < 0)
			dprintf(0, "Unable to seteuid %d: %s\n",
			    cred_uid, strerror(errno));
		else
			eff_uid = cred_uid;
	}
}

