OpenSolaris_b135/lib/libproject/common/setproject.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/task.h>
#include <sys/types.h>
#include <unistd.h>

#include <ctype.h>
#include <project.h>
#include <rctl.h>
#include <secdb.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <nss_dbdefs.h>
#include <pwd.h>
#include <pool.h>
#include <libproc.h>
#include <priv.h>
#include <priv_utils.h>
#include <zone.h>
#include <sys/pool.h>
#include <sys/pool_impl.h>
#include <sys/rctl_impl.h>

static void
xstrtolower(char *s)
{
	for (; *s != '\0'; s++)
		*s = tolower(*s);
}

static void
remove_spaces(char *s)
{
	char *current;
	char *next;

	current = next = s;

	while (*next != '\0') {
		while (isspace(*next))
		    next++;
		*current++ = *next++;
	}
	*current = '\0';
}

int
build_rctlblk(rctlblk_t *blk, int comp_num, char *component)
{
	char *signam;
	int sig = 0;
	uint_t act = rctlblk_get_local_action(blk, &sig);

	if (comp_num == 0) {
		/*
		 * Setting privilege level for resource control block.
		 */
		xstrtolower(component);

		if (strcmp("basic", component) == 0) {
			rctlblk_set_privilege(blk, RCPRIV_BASIC);
			return (0);
		}

		if (strcmp("priv", component) == 0 ||
		    strcmp("privileged", component) == 0) {
			rctlblk_set_privilege(blk, RCPRIV_PRIVILEGED);
			return (0);
		}

		return (-1);
	}

	if (comp_num == 1) {

		/*
		 * Setting value for resource control block.
		 */
		unsigned long long val;
		char *t;

		/* Negative numbers are not allowed */
		if (strchr(component, '-') != NULL)
			return (-1);

		errno = 0;
		val = strtoull(component, &t, 10);
		if (errno != 0 || t == component || *t != '\0')
			return (-1);

		rctlblk_set_value(blk, (rctl_qty_t)val);
		return (0);
	}

	/*
	 * Setting one or more actions on this resource control block.
	 */
	if (comp_num >= 2) {
		if (strcmp("none", component) == 0) {
			rctlblk_set_local_action(blk, 0, 0);
			return (0);
		}

		if (strcmp("deny", component) == 0) {
			act |= RCTL_LOCAL_DENY;

			rctlblk_set_local_action(blk, act, sig);

			return (0);
		}

		/*
		 * The last, and trickiest, form of action is the signal
		 * specification.
		 */
		if ((signam = strchr(component, '=')) == NULL)
			return (-1);

		*signam++ = '\0';

		if (strcmp("sig", component) == 0 ||
		    strcmp("signal", component) == 0) {
			if (strncmp("SIG", signam, 3) == 0)
				signam += 3;

			if (str2sig(signam, &sig) == -1)
				return (-1);

			act |= RCTL_LOCAL_SIGNAL;

			rctlblk_set_local_action(blk, act, sig);

			return (0);
		}
	}
	return (-1);
}

/*
 * States:
 */
#define	INPAREN		0x1

/*
 * Errors:
 */
#define	SETFAILED	(-1)
#define	COMPLETE	1
#define	NESTING		2
#define	UNCLOSED	3
#define	CLOSEBEFOREOPEN	4
#define	BADSPEC		5

static int
rctl_set(char *ctl_name, char *val, struct ps_prochandle *Pr, int flags)
{
	int error = 0;
	uint_t component = 0;
	int valuecount = 0;
	uint_t state = 0;
	char *component_head;
	rctlblk_t *blk;
	rctlblk_t *ablk;
	int project_entity = 0;
	int count = 0;
	char *tmp;
	int local_action;

	/* We cannot modify a zone resource control */
	if (strncmp(ctl_name, "zone.", strlen("zone.")) == 0) {
		return (SETFAILED);
	}

	remove_spaces(val);

	/*
	 * As we are operating in a new task, both process and task
	 * rctls are referenced by this process alone.  Tear down
	 * matching process and task rctls only.
	 *
	 * blk will be the RCPRIV_SYSTEM for this resource control,
	 * populated by the last pr_setrctl().
	 */
	if ((strncmp(ctl_name, "process.", strlen("process.")) == 0) ||
	    (strncmp(ctl_name, "task.", strlen("task.")) == 0)) {

		if ((blk = (rctlblk_t *)malloc(rctlblk_size())) == NULL) {
			return (SETFAILED);
		}


		while (pr_getrctl(Pr, ctl_name, NULL, blk, RCTL_FIRST) != -1 &&
		    rctlblk_get_privilege(blk) != RCPRIV_SYSTEM) {
			(void) pr_setrctl(Pr, ctl_name, NULL, blk, RCTL_DELETE);
		}

	} else if (strncmp(ctl_name, "project.", strlen("project.")) == 0) {
		project_entity = 1;

		/* Determine how many attributes we'll be setting */
		for (tmp = val; *tmp != '\0'; tmp++) {
			if (*tmp == '(')
				count++;
		}

		/* Allocate sufficient memory for rctl blocks */
		if ((count == 0) || ((ablk =
		    (rctlblk_t *)malloc(rctlblk_size() * count)) == NULL)) {
			return (SETFAILED);
		}
		blk = ablk;

		/*
		 * In order to set the new rctl's local_action, we'll need the
		 * current value of global_flags.  We obtain global_flags by
		 * performing a pr_getrctl().
		 *
		 * The ctl_name has been verified as valid, so we have no reason
		 * to suspect that pr_getrctl() will return an error.
		 */
		(void) pr_getrctl(Pr, ctl_name, NULL, blk, RCTL_FIRST);

	} else {
		return (SETFAILED);
	}

	/*
	 * Set initial local action based on global deny properties.
	 */
	rctlblk_set_privilege(blk, RCPRIV_PRIVILEGED);
	rctlblk_set_value(blk, 0);
	rctlblk_set_local_flags(blk, 0);

	if (rctlblk_get_global_flags(blk) & RCTL_GLOBAL_DENY_ALWAYS)
		local_action = RCTL_LOCAL_DENY;
	else
		local_action = RCTL_LOCAL_NOACTION;

	rctlblk_set_local_action(blk, local_action, 0);

	for (; ; val++) {
		switch (*val) {
			case '(':
				if (state & INPAREN) {
					error = NESTING;
					break;
				}

				state |= INPAREN;
				component_head = (char *)val + 1;

				break;
			case ')':
				if (state & INPAREN) {
					*val = '\0';
					if (component < 2) {
						error = BADSPEC;
						break;
					}
					if (build_rctlblk(blk, component,
					    component_head) == -1) {
						error = BADSPEC;
						break;
					}
					state &= ~INPAREN;
					component = 0;
					valuecount++;

					if (project_entity &&
					    (rctlblk_get_privilege(blk) ==
					    RCPRIV_BASIC)) {
						error = SETFAILED;
					} else if (project_entity) {
						if (valuecount > count)
							return (SETFAILED);

						if (valuecount != count)
							blk = RCTLBLK_INC(ablk,
								valuecount);
					} else {
						if (pr_setrctl(Pr, ctl_name,
						    NULL, blk, RCTL_INSERT) ==
						    -1)
							error = SETFAILED;
					}

					/* re-initialize block */
					if (!project_entity ||
					    (valuecount != count)) {
						rctlblk_set_privilege(blk,
						    RCPRIV_PRIVILEGED);
						rctlblk_set_value(blk, 0);
						rctlblk_set_local_flags(blk, 0);
						rctlblk_set_local_action(blk,
						    local_action, 0);
					}
				} else {
					error = CLOSEBEFOREOPEN;
				}
				break;
			case ',':
				if (state & INPAREN) {
					*val = '\0';
					if (build_rctlblk(blk, component,
					    component_head) == -1)
						error = BADSPEC;

					component++;
					component_head = (char *)val + 1;

				}
				break;
			case '\0':
				if (valuecount == 0)
					error = BADSPEC;
				else if (state & INPAREN)
					error = UNCLOSED;
				else
					error = COMPLETE;
				break;
			default:
				if (!(state & INPAREN))
					error = BADSPEC;
				break;
		}

		if (error)
			break;
	}

	if (project_entity) {
		blk = ablk;
		if (pr_setprojrctl(Pr, ctl_name, blk, count, flags) == -1)
			error = SETFAILED;
	}

	free(blk);

	if (valuecount == 0)
		error = BADSPEC;

	if (error != COMPLETE)
		return (error);

	return (0);
}

static int
rctlwalkfunc(const char *name, void *data)
{

	if (strcmp(name, (char *)data) == 0)
		return (-1);
	else
		return (0);

}

/*
 * This routine determines if /dev/pool device is present on the system and
 * pools are currently enabled.  We want to do this directly from libproject
 * without using libpool's pool_get_status() routine because pools could be
 * completely removed from the system.  Return 1 if pools are enabled, or
 * 0 otherwise.  When used inside local zones, always pretend that pools
 * are disabled because binding is not allowed and we're already in the
 * right pool.
 */
static int
pools_enabled(void)
{
	pool_status_t status;
	int fd;

	if (getzoneid() != GLOBAL_ZONEID)
		return (0);
	if ((fd = open("/dev/pool", O_RDONLY)) < 0)
		return (0);
	if (ioctl(fd, POOL_STATUSQ, &status) < 0) {
		(void) close(fd);
		return (0);
	}
	(void) close(fd);
	return (status.ps_io_state);
}

/*
 * A pool_name of NULL means to attempt to bind to the default pool.
 * If the "force" flag is non-zero, the value of "system.bind-default" will be
 * ignored, and the process will be bound to the default pool if one exists.
 */
static int
bind_to_pool(const char *pool_name, pid_t pid, int force)
{
	pool_value_t *pvals[] = { NULL, NULL };
	pool_t **pools;
	uint_t nelem;
	uchar_t bval;
	pool_conf_t *conf;
	const char *nm;
	int retval;

	if ((conf = pool_conf_alloc()) == NULL)
		return (-1);
	if (pool_conf_open(conf, pool_dynamic_location(), PO_RDONLY) < 0) {
		/*
		 * Pools configuration file is corrupted; allow logins.
		 */
		pool_conf_free(conf);
		return (0);
	}
	if (pool_name != NULL && pool_get_pool(conf, pool_name) != NULL) {
		/*
		 * There was a project.pool entry, and the pool it refers to
		 * is a valid (active) pool.
		 */
		(void) pool_conf_close(conf);
		pool_conf_free(conf);
		if (pool_set_binding(pool_name, P_PID, pid) != PO_SUCCESS) {
			if (pool_error() != POE_SYSTEM)
				errno = EINVAL;
			return (-1);
		}
		return (0);
	}

	/*
	 * Bind to the pool with 'pool.default' = 'true' if
	 * 'system.bind-default' = 'true'.
	 */
	if ((pvals[0] = pool_value_alloc()) == NULL) {
		pool_conf_close(conf);
		pool_conf_free(conf);
		return (-1);
	}
	if (!force && pool_get_property(conf, pool_conf_to_elem(conf),
	    "system.bind-default", pvals[0]) != POC_BOOL ||
	    pool_value_get_bool(pvals[0], &bval) != PO_SUCCESS ||
	    bval == PO_FALSE) {
		pool_value_free(pvals[0]);
		pool_conf_close(conf);
		pool_conf_free(conf);
		errno = pool_name == NULL ? EACCES : ESRCH;
		return (-1);
	}
	(void) pool_value_set_name(pvals[0], "pool.default");
	pool_value_set_bool(pvals[0], PO_TRUE);
	if ((pools = pool_query_pools(conf, &nelem, pvals)) == NULL) {
		/*
		 * No default pools exist.
		 */
		pool_value_free(pvals[0]);
		pool_conf_close(conf);
		pool_conf_free(conf);
		errno = pool_name == NULL ? EACCES : ESRCH;
		return (-1);
	}
	if (nelem != 1 ||
	    pool_get_property(conf, pool_to_elem(conf, pools[0]), "pool.name",
	    pvals[0]) != POC_STRING) {
		/*
		 * Configuration is invalid.
		 */
		free(pools);
		pool_value_free(pvals[0]);
		(void) pool_conf_close(conf);
		pool_conf_free(conf);
		return (0);
	}
	free(pools);
	(void) pool_conf_close(conf);
	pool_conf_free(conf);
	(void) pool_value_get_string(pvals[0], &nm);
	if (pool_set_binding(nm, P_PID, pid) != PO_SUCCESS) {
		if (pool_error() != POE_SYSTEM)
			errno = EINVAL;
		retval = -1;
	} else {
		retval = 0;
	}
	pool_value_free(pvals[0]);
	return (retval);
}

/*
 * Changes the assigned project, task and resource pool of a stopped target
 * process.
 *
 * We may not have access to the project table if our target process is in
 * getprojbyname()'s execution path. Similarly, we may not be able to get user
 * information if the target process is in getpwnam()'s execution path. Thus we
 * give the caller the option of skipping these checks by providing a pointer to
 * a pre-validated project structure in proj (whose name matches project_name)
 * and taking responsibility for ensuring that the target process' owner is a
 * member of the target project.
 *
 * Callers of this function should always provide a pre-validated project
 * structure in proj unless they can be sure that the target process will never
 * be in setproject_proc()'s execution path.
 */

projid_t
setproject_proc(const char *project_name, const char *user_name, int flags,
    pid_t pid, struct ps_prochandle *Pr, struct project *proj)
{
	char pwdbuf[NSS_BUFLEN_PASSWD];
	char prbuf[PROJECT_BUFSZ];
	projid_t projid;
	struct passwd pwd;
	int i;
	int unknown = 0;
	int ret = 0;
	kva_t *kv_array;
	struct project local_proj; /* space to store proj if not provided */
	const char *pool_name = NULL;

	if (project_name != NULL) {
		/*
		 * Sanity checks.
		 */
		if (strcmp(project_name, "") == 0 ||
		    user_name == NULL) {
			errno = EINVAL;
			return (SETPROJ_ERR_TASK);
		}

		/*
		 * If proj is NULL, acquire project information to ensure that
		 * project_name is a valid project, and confirm that user_name
		 * exists and is a member of the specified project.
		 */
		if (proj == NULL) {
			if ((proj = getprojbyname(project_name, &local_proj,
			    prbuf, PROJECT_BUFSZ)) == NULL) {
				errno = ESRCH;
				return (SETPROJ_ERR_TASK);
			}

			if (getpwnam_r(user_name, &pwd,
			    pwdbuf, NSS_BUFLEN_PASSWD) == NULL) {
				errno = ESRCH;
				return (SETPROJ_ERR_TASK);
			}
			/*
			 * Root can join any project.
			 */
			if (pwd.pw_uid != (uid_t)0 &&
			    !inproj(user_name, project_name, prbuf,
			    PROJECT_BUFSZ)) {
				errno = ESRCH;
				return (SETPROJ_ERR_TASK);
			}
		}
		projid = proj->pj_projid;
	} else {
		projid = getprojid();
	}


	if ((kv_array = _str2kva(proj->pj_attr, KV_ASSIGN,
	    KV_DELIMITER)) != NULL) {
		for (i = 0; i < kv_array->length; i++) {
			if (strcmp(kv_array->data[i].key,
			    "project.pool") == 0) {
				pool_name = kv_array->data[i].value;
			}
			if (strcmp(kv_array->data[i].key, "task.final") == 0) {
				flags |= TASK_FINAL;
			}
		}
	}

	/*
	 * Bind process to a pool only if pools are configured
	 */
	if (pools_enabled() == 1) {
		char *old_pool_name;
		/*
		 * Attempt to bind to pool before calling
		 * settaskid().
		 */
		old_pool_name = pool_get_binding(pid);
		if (bind_to_pool(pool_name, pid, 0) != 0) {
			if (old_pool_name)
				free(old_pool_name);
			_kva_free(kv_array);
			return (SETPROJ_ERR_POOL);
		}
		if (pr_settaskid(Pr, projid, flags & TASK_MASK) == -1) {
			int saved_errno = errno;

			/*
			 * Undo pool binding.
			 */
			(void) bind_to_pool(old_pool_name, pid, 1);
			if (old_pool_name)
				free(old_pool_name);
			_kva_free(kv_array);
			/*
			 * Restore errno
			 */
			errno = saved_errno;
			return (SETPROJ_ERR_TASK);
		}
		if (old_pool_name)
			free(old_pool_name);
	} else {
		/*
		 * Pools are not configured, so simply create new task.
		 */
		if (pr_settaskid(Pr, projid, flags & TASK_MASK) == -1) {
			_kva_free(kv_array);
			return (SETPROJ_ERR_TASK);
		}
	}

	if (project_name == NULL) {
		/*
		 * In the case that we are starting a new task in the
		 * current project, we are finished, since the current
		 * resource controls will still apply. (Implicit behaviour:
		 * a project must be entirely logged out before name
		 * service changes will take effect.)
		 */
		_kva_free(kv_array);
		return (projid);
	}

	if (kv_array == NULL)
		return (0);

	for (i = 0; i < kv_array->length; i++) {
		/*
		 * Providing a special, i.e. a non-resource control, key?  Then
		 * parse that key here and end with "continue;".
		 */

		/*
		 * For generic bindings, the kernel performs the binding, as
		 * these are resource controls advertised by kernel subsystems.
		 */

		/*
		 * Check for known attribute name.
		 */
		errno = 0;
		if (rctl_walk(rctlwalkfunc, (void *)kv_array->data[i].key)
		    == 0)
			continue;
		if (errno) {
			_kva_free(kv_array);
			return (SETPROJ_ERR_TASK);
		}

		ret = rctl_set(kv_array->data[i].key,
		    kv_array->data[i].value, Pr, flags & TASK_PROJ_MASK);

		if (ret && unknown == 0) {
			/*
			 * We only report the first failure.
			 */
			unknown = i + 1;
		}

		if (ret && ret != SETFAILED) {
			/*
			 * We abort if we couldn't set a component, but if
			 * it's merely that the system didn't recognize it, we
			 * continue, as this could be a third party attribute.
			 */
			break;
		}
	}
	_kva_free(kv_array);

	return (unknown);
}

projid_t
setproject(const char *project_name, const char *user_name, int flags)
{
	return (setproject_proc(project_name, user_name, flags, P_MYID, NULL,
	    NULL));
}


priv_set_t *
setproject_initpriv(void)
{
	static priv_t taskpriv = PRIV_PROC_TASKID;
	static priv_t rctlpriv = PRIV_SYS_RESOURCE;
	static priv_t poolpriv = PRIV_SYS_RES_CONFIG;
	static priv_t schedpriv = PRIV_PROC_PRIOCNTL;
	int res;

	priv_set_t *nset;

	if (getzoneid() == GLOBAL_ZONEID) {
		res = __init_suid_priv(0, taskpriv, rctlpriv, poolpriv,
		    schedpriv, (char *)NULL);
	} else {
		res = __init_suid_priv(0, taskpriv, rctlpriv, (char *)NULL);
	}

	if (res != 0)
		return (NULL);

	nset = priv_allocset();
	if (nset != NULL) {
		priv_emptyset(nset);
		(void) priv_addset(nset, taskpriv);
		(void) priv_addset(nset, rctlpriv);
		/*
		 * Only need these if we need to change pools, which can
		 * only happen if the target is in the global zone.  Rather
		 * than checking the target's zone just check our own
		 * (since if we're in a non-global zone we won't be able
		 * to control processes in other zones).
		 */
		if (getzoneid() == GLOBAL_ZONEID) {
			(void) priv_addset(nset, poolpriv);
			(void) priv_addset(nset, schedpriv);
		}
	}
	return (nset);
}