/*
 * UNFS3 user and group id handling
 * (C) 2003, Pascal Schmidt
 * see file LICENSE for license details
 */

#include "config.h"

#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <rpc/rpc.h>
#include <stdlib.h>
#include <syslog.h>
#include <unistd.h>

#include "nfs.h"
#include "mount.h"
#include "daemon.h"
#include "user.h"
#include "backend.h"
#include "Config/exports.h"

/* user and group id we squash to */
static uid_t squash_uid = 65534;
static gid_t squash_gid = 65534;

/* whether we can use seteuid/setegid */
static int can_switch = TRUE;

/*
 * initialize group and user id used for squashing
 */
void get_squash_ids(void)
{
    struct passwd *passwd;

    if (can_switch) {
	passwd = getpwnam("nobody");
	if (passwd) {
	    squash_uid = passwd->pw_uid;
	    squash_gid = passwd->pw_gid;
	} else {
	    squash_uid = 65534;
	    squash_gid = 65534;
	}
    }
}

/*
 * mangle an id
 */
static int mangle(int id, int squash)
{
    if (!can_switch || (exports_opts & OPT_ALL_SQUASH))
	return squash;
    else if (exports_opts & OPT_NO_ROOT_SQUASH)
	return id;
    else if (id == 0)
	return squash;
    else
	return id;
}

/*
 * return user id of a request
 */
int get_uid(struct svc_req *req)
{
    struct authunix_parms *auth = (void *) req->rq_clntcred;

    if (req->rq_cred.oa_flavor == AUTH_UNIX)
	return mangle(auth->aup_uid, squash_uid);
    else
	return squash_uid;	       /* fallback if no uid given */
}

/*
 * return group id of a request
 */
static int get_gid(struct svc_req *req)
{
    struct authunix_parms *auth = (void *) req->rq_clntcred;

    if (req->rq_cred.oa_flavor == AUTH_UNIX)
	return mangle(auth->aup_gid, squash_gid);
    else
	return squash_gid;	       /* fallback if no gid given */
}

/*
 * check whether a request comes from a given user id
 */
int is_owner(int owner, struct svc_req *req)
{
    return (int) (owner == get_uid(req));
}

/*
 * check if a request comes from somebody who has a given group id
 */
int has_group(int group, struct svc_req *req)
{
    struct authunix_parms *auth = (void *) req->rq_clntcred;
    unsigned int i;

    if (req->rq_cred.oa_flavor == AUTH_UNIX) {
	if (mangle(auth->aup_gid, squash_gid) == group)
	    return TRUE;

	/* search groups */
	for (i = 0; i < auth->aup_len; i++)
	    if (mangle(auth->aup_gids[i], squash_gid) == group)
		return TRUE;
    }

    return FALSE;
}

/*
 * switch user and group id to values listed in request
 */
void switch_user(struct svc_req *req)
{
    int uid, gid;

    if (!can_switch)
	return;

    if (opt_singleuser || (backend_getuid() != 0)) {
	/* 
	 * have uid/gid functions behave correctly by squashing
	 * all user and group ids to the current values
	 *
	 * otherwise ACCESS would malfunction
	 */
	squash_uid = backend_getuid();
	squash_gid = backend_getgid();

	can_switch = FALSE;
	return;
    }

    backend_setegid(0);
    backend_seteuid(0);
    gid = backend_setegid(get_gid(req));
    uid = backend_seteuid(get_uid(req));

    if (uid == -1 || gid == -1) {
	logmsg(LOG_EMERG, "euid/egid switching failed, aborting");
	daemon_exit(CRISIS);
    }
}

/*
 * re-switch to root for reading executable files
 */
void read_executable(struct svc_req *req, struct stat buf)
{
    int have_exec = 0;

    /* bail out if read permission is there */
    if (buf.st_mode & S_IROTH)
	return;
    else if ((buf.st_mode & S_IRUSR) && is_owner(buf.st_uid, req))
	return;
    else if ((buf.st_mode & S_IRGRP) && has_group(buf.st_gid, req))
	return;

    /* check for execute permission */
    if (buf.st_mode & S_IXOTH)
	have_exec = 1;
    else if ((buf.st_mode & S_IXUSR) && is_owner(buf.st_uid, req))
	have_exec = 1;
    else if ((buf.st_mode & S_IXGRP) && has_group(buf.st_gid, req))
	have_exec = 1;

    if (have_exec) {
	backend_setegid(0);
	backend_seteuid(0);
    }
}


syntax highlighted by Code2HTML, v. 0.9.1