/*
 * auth_clnt.c
 *
 * This module takes care of request authorization.
 *
 * Authors:	Don Becker, <becker@super.org>
 *			Rick Sladkey, <jrs@world.std.com>
 *			Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
 *			Olaf Kirch, <okir@monad.swb.de>
 *
 * 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 "system.h"
#include "auth.h"
#include "logging.h"
#include "fsxid.h"

#ifndef svc_getcaller
#define svc_getcaller(x) ((struct sockaddr_in *) &(x)->xp_rtaddr.buf)
#endif

#ifdef FSUID_PRESENT
static void setfsids(uid_t, gid_t, gid_t *, int);
#else
static void seteids(uid_t, gid_t, gid_t *, int);
#endif /* FSUID_PRESENT */

uid_t root_uid = 0;
uid_t auth_uid = 0;			       /* Current effective user ids */
gid_t auth_gid = 0;
GETGROUPS_T auth_gids[NGRPS];		       /* Current supplementary gids */
int auth_gidlen = -1;
uid_t cred_uid = 0;
gid_t cred_gid = 0;
gid_t *cred_gids = NULL;
int cred_len = 0;

#if defined(HAVE_AUTHDES_GETUCRED) && !defined(HAVE_AUTHDES_GETUCRED_DECL)

/* authdes_getucred is not exported in svcauth.h even if present. */
extern int authdes_getucred(caddr_t credptr, short *uid,
			    short *gid, short *nrgids, int *groups);
#endif

/*
 * For an RPC request, look up the NFS client info along with the
 * list of directories exported to that client.
 */
nfs_client *
auth_clnt(struct svc_req *rqstp)
{
	nfs_client *cp = NULL;
	struct in_addr addr = ((struct sockaddr_in*)svc_getcaller(rqstp->rq_xprt))->sin_addr;

	/* Get the client and list of exports */
	if ((cp = auth_clientbyaddr(addr)) != NULL)
		return cp;

	/* We don't know you */
	if (trace_spoof) {
		dbg_printf(__FILE__, __LINE__, L_ERROR,
			   "Unauthorized access by NFS client %s.\n",
			   inet_ntoa(addr));
	}

	return (NULL);
}

nfs_mount *
auth_path(nfs_client * cp, struct svc_req * rqstp, char *path)
{
	nfs_mount *mp;

	/* Check if the specified client is permitted to access this */
	if ((mp = auth_match_mount(cp, path)) == NULL) {
		if (cp->flags != 0 || trace_spoof) {
			dbg_printf(__FILE__, __LINE__, L_ERROR,
				   "NFS client %s tried to access %s\n",
				   cp->clnt_name, path);
		}
		return NULL;
	}

	/* Check request originated on a privileged port. */
	if (!allow_non_root && mp->o.secure_port
	    && !SECURE_PORT(((struct sockaddr_in*)svc_getcaller(rqstp->rq_xprt))->sin_port)) {
		dbg_printf(__FILE__, __LINE__, L_ERROR,
			   "NFS request from %s originated on insecure port, %s\n",
			   cp->clnt_name, "psychoanalysis suggested");
		return (NULL);
	}

	if (log_level_enabled(D_AUTH)) {
		dbg_printf(__FILE__, __LINE__, D_AUTH,
			   "auth_path(%s): mount point %s, (%s%s%s%s%s)\n",
			   path, mp->path,
			   mp->o.all_squash ? "all_squash " : (mp->o.
							       root_squash ?
							       "root_squash "
							       : ""),
			   (mp->o.uidmap == map_daemon) ? "uidmap " : "",
			   mp->o.secure_port ? "secure " : "insecure ",
			   mp->o.link_relative ? "linkrel " : "",
			   mp->o.read_only ? "ro" : "rw");
	}

	return mp;
}

void
auth_user(nfs_mount * mp, struct svc_req *rqstp)
{
	uid_t cuid;
	gid_t cgid;
	GETGROUPS_T cgids[NGRPS];
	int squash = mp->o.all_squash;
	int cred_set, i, clen;

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

		unix_cred = (struct authunix_parms *) rqstp->rq_clntcred;
		cred_uid = (uid_t) unix_cred->aup_uid;
		cred_gid = (gid_t) unix_cred->aup_gid;
		cred_len = ((unix_cred->aup_len >= 0) ? (int) unix_cred->aup_len : 0);
		cred_gids = (gid_t *) unix_cred->aup_gids;
		cred_set = 1;
#ifdef HAVE_AUTHDES_GETUCRED
	} else if (rqstp->rq_cred.oa_flavor == AUTH_DES) {
		static GETGROUPS_T des_gids[NGRPS];
		struct authdes_cred *cred;
		short grplen = NGRPS;
		int i;

		cred = (struct authdes_cred *) rqstp->rq_clntcred;
		i = authdes_getucred(cred, &cred_uid, &cred_gid,
				     &grplen, des_gids);
		if (i != 0 && grplen >= 0) {
			cred_set = 1;
			cred_len = grplen;
			cred_gids = des_gids;
			if (cred_len > NGRPS)
				cred_len = NGRPS;
		}
#endif
	}

	/* We will want to support AUTH_DES/AUTH_KRB one day,
	 * but for now we treat all other authentication flavor
	 * as AUTH_NULL.
	 */
	if (!cred_set)
		squash = 1;

	if (!squash) {
		/* Do the uid/gid mapping here.
		 * Note that we check cred_uid (which is the native uid_t),
		 * not unix_cred->aup_uid to avoid the uid masking bug.
		 */
		if (cred_len < 0)
			cred_len = 0;
		else if (cred_len > NGRPS)
			cred_len = NGRPS;

		cuid = luid(cred_uid, mp, rqstp);
		cgid = lgid(cred_gid, mp, rqstp);
		clen = cred_len;
		for (i = 0; i < cred_len; i++)
			cgids[i] = lgid(cred_gids[i], mp, rqstp);
	} else {
		/* On systems that have 32bit uid_t in user space but
		 * 16bit in the kernel, we need to truncate the
		 * nobody ID (default -2).
		 */
#if !defined(HAVE_BROKEN_SETFSUID)
		cuid = mp->o.nobody_uid;
		cgid = mp->o.nobody_gid;
#else
		cuid = (unsigned short) mp->o.nobody_uid;
		cgid = (unsigned short) mp->o.nobody_gid;
#endif
		/* Construct a list of one gid. */
		cgids[0] = cgid;
		clen = 1;
	}

	/* This code is a little awkward because setfsuid has been present
	 * in the Linux kernel for quite some time but not in libc.
	 * The startup code tests for the setfsuid syscall and sets
	 * have_setfsuid accordingly.
	 *
	 * The code becomes even more awkward as of glibc 2.1 because
	 * we now have 32bit user-land uid_t, 16bit kernel uid_t, and
	 * a setfsuid function that rejects any uids that have the
	 * upper 16 bits set (including our default nobody uid -2).
	 */
#if defined(HAVE_SETFSUID)
	setfsids(cuid, cgid, cgids, clen);
#else
#if defined(MAYBE_HAVE_SETFSUID)
	if (have_setfsuid)
		setfsids(cuid, cgid, cgids, clen);
	else
#endif
		seteids(cuid, cgid, cgids, clen);
#endif
}

/*
 * The following functions deal with setting the client's uid/gid.
 */
void
auth_override_uid(uid_t uid)
{
#if defined(HAVE_BROKEN_SETFSUID)
	uid = (unsigned short) uid;
#endif
#if defined(HAVE_SETFSUID)
	setfsuid(uid);
#else
#if defined(MAYBE_HAVE_SETFSUID)
	if (have_setfsuid)
		setfsuid(uid);
	else
#endif
		seteuid(uid);
#endif
}

#if defined(HAVE_SETFSUID) || defined(MAYBE_HAVE_SETFSUID)
static void
setfsids(uid_t cred_uid, gid_t cred_gid, gid_t * cred_gids, int cred_len)
{
	setfsuid(cred_uid);
	setfsgid(cred_gid);

	/* First, set the user ID. */
	if (auth_uid != cred_uid) {
		if (setfsuid(cred_uid) < 0)
			dbg_printf(__FILE__, __LINE__, L_ERROR,
				   "Unable to setfsuid %d: %s\n", cred_uid,
				   strerror(errno));
		else
			auth_uid = cred_uid;
	}

	/* Next, the group ID. */
	if (auth_gid != cred_gid) {
		if (setfsgid(cred_gid) < 0)
			dbg_printf(__FILE__, __LINE__, L_ERROR,
				   "Unable to setfsgid %d: %s\n", cred_gid,
				   strerror(errno));
		else
			auth_gid = cred_gid;
	}
#ifdef HAVE_SETGROUPS
	/* Finally, set the supplementary group IDs if possible. */
	if (cred_len < 0 || cred_len > NGRPS)
		dbg_printf(__FILE__, __LINE__, L_ERROR,
			   "Negative or huge cred_len: %d\n", cred_len);
	else if (cred_len != auth_gidlen
		 || memcmp(cred_gids, auth_gids,
			   auth_gidlen * sizeof(gid_t))) {
		if (setgroups(cred_len, cred_gids) < 0)
			dbg_printf(__FILE__, __LINE__, L_ERROR,
				   "Unable to setgroups: %s\n",
				   strerror(errno));
		else {
			memcpy(auth_gids, cred_gids,
			       cred_len * sizeof(gid_t));
			auth_gidlen = cred_len;
		}
	}
#endif /* HAVE_SETGROUPS */

}
#endif

#if !defined(HAVE_SETFSUID)
static void
seteids(uid_t cred_uid, gid_t cred_gid, gid_t * cred_gids, int cred_len)
{
	/*
	 * To set any IDs we first need to be root. What a pain.
	 * Unless we're under Cygwin, in which case we don't need
	 * to be root, but we do need to be running under an account
	 * that has the proper security rights (ex, SYSTEM).
	 */

	/* First set the group ID. */
	if (auth_gid != cred_gid) {
#if !defined(__CYGWIN__)
		if (auth_uid != root_uid) {
			if (seteuid(root_uid) < 0)
				dbg_printf(__FILE__, __LINE__, L_ERROR,
					   "Unable to set effective uid for root: seteuid(%d): %s\n",
					   root_uid, strerror(errno));
			else
				auth_uid = root_uid;
		}
#endif /* ! __CYGWIN__ */
		if (setegid(cred_gid) < 0)
			dbg_printf(__FILE__, __LINE__, L_ERROR,
				   "Unable to setegid(%d): %s\n", cred_gid,
				   strerror(errno));
		else
			auth_gid = cred_gid;
	}
#ifdef HAVE_SETGROUPS
	/* Next set the supplementary group IDs if possible. */
	if (cred_len < 0 || cred_len > NGRPS)
		dbg_printf(__FILE__, __LINE__, L_ERROR,
			   "Negative or huge cred_len: %d\n", cred_len);
	else if (cred_len != auth_gidlen
		 || memcmp(cred_gids, auth_gids,
			   auth_gidlen * sizeof(gid_t))) {
#if !defined(__CYGWIN__)
		if (auth_uid != root_uid) {
			if (seteuid(root_uid) < 0)
				dbg_printf(__FILE__, __LINE__, L_ERROR,
					   "Unable to set effective uid for root: seteuid(%d): %s\n",
					   root_uid, strerror(errno));
			else
				auth_uid = root_uid;
		}
#endif /* ! __CYGWIN__ */
		if (setgroups(cred_len, cred_gids) < 0)
			dbg_printf(__FILE__, __LINE__, L_ERROR,
				   "Unable to setgroups: %s\n",
				   strerror(errno));
		else {
			memcpy(auth_gids, cred_gids,
			       cred_len * sizeof(gid_t));
			auth_gidlen = cred_len;
		}
	}
#endif /* HAVE_SETGROUPS */

	/* Finally, set the user ID. */
	if (auth_uid != cred_uid) {
#if !defined(__CYGWIN__)
		if (auth_uid != root_uid && seteuid(root_uid) < 0)
			dbg_printf(__FILE__, __LINE__, L_ERROR,
				   "Unable to set effective uid for root: seteuid(%d): %s\n",
				   root_uid, strerror(errno));
#endif /* ! __CYGWIN__ */
		if (seteuid(cred_uid) < 0)
			dbg_printf(__FILE__, __LINE__, L_ERROR,
				   "Unable to seteuid(%d): %s\n", cred_uid,
				   strerror(errno));
		else
			auth_uid = cred_uid;
	}
}
#endif
