summaryrefslogtreecommitdiff
path: root/usr/src/lib/libproject/common/setproject.c
diff options
context:
space:
mode:
authorstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
committerstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
commit7c478bd95313f5f23a4c958a745db2134aa03244 (patch)
treec871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/lib/libproject/common/setproject.c
downloadillumos-gate-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz
OpenSolaris Launch
Diffstat (limited to 'usr/src/lib/libproject/common/setproject.c')
-rw-r--r--usr/src/lib/libproject/common/setproject.c681
1 files changed, 681 insertions, 0 deletions
diff --git a/usr/src/lib/libproject/common/setproject.c b/usr/src/lib/libproject/common/setproject.c
new file mode 100644
index 0000000000..2303576d32
--- /dev/null
+++ b/usr/src/lib/libproject/common/setproject.c
@@ -0,0 +1,681 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2005 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 <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>
+
+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 error = 0;
+ uint_t component = 0;
+ int valuecount = 0;
+ uint_t state = 0;
+ char *component_head;
+ rctlblk_t *blk;
+
+ remove_spaces(val);
+ if ((blk = malloc(rctlblk_size())) == NULL) {
+ return (SETFAILED);
+ }
+
+ /*
+ * Tear down everything with this ctl name.
+ */
+ 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);
+ }
+
+ /*
+ * 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)
+ rctlblk_set_local_action(blk, RCTL_LOCAL_DENY, 0);
+ else
+ rctlblk_set_local_action(blk, RCTL_LOCAL_NOACTION, 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 (pr_setrctl(Pr, ctl_name, NULL, blk,
+ RCTL_INSERT) == -1)
+ error = SETFAILED;
+
+ /* re-initialize block */
+ 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)
+ rctlblk_set_local_action(blk,
+ RCTL_LOCAL_DENY, 0);
+ else
+ rctlblk_set_local_action(blk,
+ RCTL_LOCAL_NOACTION, 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;
+ }
+
+ 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 */
+
+ 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();
+ }
+
+ /*
+ * Only bind to a pool if pools are configured.
+ */
+ if (pools_enabled() == 1) {
+ const char *pool_name = NULL;
+ char *old_pool_name;
+ int taskflags = flags;
+ /*
+ * Attempt to bind to pool before calling
+ * settaskid().
+ */
+ 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;
+ break;
+ }
+ if (strcmp(kv_array->data[i].key,
+ "task.final") == 0) {
+ taskflags |= TASK_FINAL;
+ }
+ }
+ }
+
+ 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, taskflags) == -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) == -1)
+ return (SETPROJ_ERR_TASK);
+ kv_array = _str2kva(proj->pj_attr, KV_ASSIGN, KV_DELIMITER);
+ }
+
+ 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);
+
+ 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);
+}