/*
* 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
* 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 (c) 2013 Gary Mills
* Copyright 2015, Joyent, Inc.
*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <alloca.h>
#include <libproc.h>
#include <libintl.h>
#include <libgen.h>
#include <limits.h>
#include <project.h>
#include <pwd.h>
#include <secdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <priv_utils.h>
#include "utils.h"
static const char *pname;
extern char **environ;
static int verbose = 0;
/* Private definitions for libproject */
struct ps_prochandle *, struct project *);
extern priv_set_t *setproject_initpriv(void);
static void usage(void);
static void preserve_error(const char *format, ...);
static int update_running_proc(int, char *, char *);
struct passwd *);
static void setproject_err(char *, char *, int, struct project *);
static void
usage(void)
{
"[-c pid | [-Fl] [command [args ...]]]\n"), pname);
exit(2);
}
int
{
int c;
int login_flag = 0;
int newproj_flag = 0;
char *shell;
char **targs;
int error;
nset = setproject_initpriv();
switch (c) {
case 'v':
verbose = 1;
break;
case 'p':
newproj_flag = 1;
break;
case 'F':
break;
case 'l':
login_flag++;
break;
case 'c':
break;
case '?':
default:
usage();
/*NOTREACHED*/
}
}
/* -c option is invalid with -F, -l, or a specified command */
usage();
}
/*
* Get user data, so that we can confirm project membership as
* well as construct an appropriate login environment.
*/
}
/*
* If no projname was specified, we're just creating a new task
* under the current project, so we can just set the new taskid.
* If our project is changing, we need to update any attendant
*/
(void) __priv_bracket(PRIV_ON);
"reached"));
else
} else {
if (error < 0)
else
}
}
if (verbose)
/*
* Validate user's shell from passwd database.
*/
else
}
if (login_flag) {
/*
* Since we've been invoked as a "simulated login", set up the
* environment.
*/
char **envnext;
1;
char *env_tz;
char *env_term;
/*
* It's possible that TERM wasn't defined in the outer
* environment.
*/
cur_term);
envnext++;
}
/*
* It is also possible that TZ wasn't defined in the outer
* environment. In that case, we must attempt to open the file
* defining the default timezone and select the appropriate
* entry. If there is no default timezone there, try
* that login uses.
*/
} else {
"TZ=")) != NULL)
else {
"TZ=");
}
}
/*
* Prefix the shell string with a hyphen, indicating a login
* shell.
*/
} else {
}
/*
* If there are no arguments, we launch the user's shell; otherwise, the
* remaining commands are assumed to form a valid command invocation
* that we can exec.
*/
} else {
}
/*
* We should never get here.
*/
return (1);
}
static int
{
struct ps_prochandle *p;
int grab_retry_count = 0;
/*
* Catch signals from terminal. There isn't much sense in
* doing anything but ignoring them since we don't do anything
* after the point we'd be capable of handling them again.
*/
/* flush stdout before grabbing the proc to avoid deadlock */
/*
* We need to grab the process, which will force it to stop execution
* until the grab is released, in order to aquire some information about
* it, such as its current project (which is achieved via an injected
* system call and therefore needs an agent) and its credentials. We
* will then need to release it again because it may be a process that
* we rely on for later calls, for example nscd.
*/
return (1);
}
if (Pcreate_agent(p) != 0) {
Prelease(p, 0);
return (1);
}
/*
* The victim process is now held. Do not call any functions
* released.
*/
/*
* The target process will soon be restarted (in case it is in newtask's
* execution path) and then stopped again. We need to ensure that our cached
* data doesn't change while the process runs so return here if the target
* process changes its user id in between our stop operations, so that we can
* try again.
*/
/* Cache required information about the process. */
if (Pcred(p, &original_prcred, 0) != 0) {
procname);
error = 1;
}
procname);
error = 1;
}
/*
* We now have all the required information, so release the target
* process and perform our sanity checks. The process needs to be
* running at this point because it may be in the execution path of the
* calls made below.
*/
Pdestroy_agent(p);
Prelease(p, 0);
/* if our data acquisition failed, then we can't continue. */
if (error) {
return (1);
}
if (newproj_flag == 0) {
/*
* Just changing the task, so set projname to the current
* project of the running process.
*/
PROJECT_BUFSZ) == NULL) {
"for projid %d"), prprojid);
return (1);
}
} else {
/*
* cache info for the project which user passed in via the
* command line
*/
PROJECT_BUFSZ) == NULL) {
return (1);
}
}
/*
* Use our cached information to verify that the owner of the running
* process is a member of proj
*/
return (1);
}
/*
* We can now safely stop the process again in order to change the
* project and taskid as required.
*/
return (1);
}
if (Pcreate_agent(p) != 0) {
Prelease(p, 0);
return (1);
}
/*
* Now that the target process is stopped, check the validity of our
* cached info. If we aren't superuser then match_user() will have
* checked to make sure that the owner of the process is in the relevant
* project. If our ruid has changed, then match_user()'s conclusion may
* be invalid.
*/
if (getuid() != 0) {
if (Pcred(p, ¤t_prcred, 0) != 0) {
Pdestroy_agent(p);
Prelease(p, 0);
procname);
return (1);
}
if (grab_retry_count++ < GRAB_RETRY_MAX)
goto pgrab_retry;
"user id %s\n"), procname);
return (1);
}
}
if (verbose)
taskid = pr_gettaskid(p);
Pdestroy_agent(p);
Prelease(p, 0);
if (error) {
/*
* error is serious enough to stop, only if negative.
* Otherwise, it simply indicates one of the resource
* control assignments failed, which is worth warning
* about.
*/
if (error < 0)
return (1);
}
if (verbose)
return (0);
}
static int
struct passwd *passwd_entry)
{
int be_su = 0;
int error;
int ind;
if (Pcred(p, &old_prcred, 0) != 0) {
return (1);
}
if (old_prpriv == NULL) {
return (1);
}
if (new_prpriv == NULL) {
return (1);
}
/*
* If the process already has the proc_taskid privilege,
* we don't need to elevate its privileges; if it doesn't,
* we try to do it here.
* As we do not wish to leave a window in which the process runs
* with elevated privileges, we make sure that the process dies
* when we go away unexpectedly.
*/
be_su = 1;
"privileges"));
(void) Punsetflags(p, PR_KLC);
return (1);
}
(void) __priv_bracket(PRIV_ON);
if (Psetpriv(p, new_prpriv) != 0) {
(void) __priv_bracket(PRIV_OFF);
"privileges"));
(void) Punsetflags(p, PR_KLC);
return (1);
}
(void) __priv_bracket(PRIV_OFF);
}
(void) __priv_bracket(PRIV_ON);
/* global_error is set by setproject_err */
}
(void) __priv_bracket(PRIV_OFF);
/* relinquish added privileges */
if (be_su) {
(void) __priv_bracket(PRIV_ON);
if (Psetpriv(p, old_prpriv) != 0) {
/*
* We shouldn't ever be in a state where we can't
* set the process back to its old creds, but we
* don't want to take the chance of leaving a
* non-privileged process with enhanced creds. So,
* release the process from libproc control, knowing
* that it will be killed.
*/
(void) __priv_bracket(PRIV_OFF);
Pdestroy_agent(p);
"for pid %d. The process was killed."),
}
(void) __priv_bracket(PRIV_OFF);
if (Punsetflags(p, PR_KLC) != 0)
"credentials. Process %d will be killed."),
}
return (error);
}
/*
* preserve_error() should be called rather than warn() by any
* function that is called while the victim process is being
* held by Pgrab.
*
* It saves a single error message to be printed until after
* the process has been released. Since multiple errors are not
* stored, any error should be considered critical.
*/
void
{
/*
* GLOBAL_ERR_SZ is pretty big. If the error is longer
* than that, just truncate it, rather than chance missing
* the error altogether.
*/
}
/*
* Given the input arguments, return the passwd structure that matches best.
* Also, since we use getpwnam() and friends, subsequent calls to this
* function will re-use the memory previously returned.
*/
static struct passwd *
{
char *tmp_name;
/*
* In order to allow users with the same UID but distinguishable
* user names to be in different projects we play a guessing
* game of which username is most appropriate. If we're checking
* for the uid of the calling process, the login name is a
* good starting point.
*/
if (is_my_uid) {
}
/*
* If the login name doesn't work, we try the first match for
* the current uid in the password file.
*/
"for uid %d"), uid);
return (NULL);
}
}
/*
* If projname wasn't supplied, we've done our best, so just return
* what we've got now. Alternatively, if newtask's invoker has
* superuser privileges, return the pw structure we've got now, with
* no further checking from inproj(). Superuser should be able to
* join any project, and the subsequent call to setproject() will
* allow this.
*/
return (pw);
char **u;
/*
* If the previous guesses didn't work, walk through all
* project members and test for UID-equivalence.
*/
PROJECT_BUFSZ) == NULL) {
projname);
return (NULL);
}
continue;
break;
}
}
return (NULL);
}
}
return (pw);
}
void
{
switch (error) {
case SETPROJ_ERR_TASK:
"been reached"));
else
gettext("could not join project \"%s\""),
projname);
break;
case SETPROJ_ERR_POOL:
"default bindings exists for project \"%s\""),
projname);
"not exist for project \"%s\""), projname);
else
"resource pool for project \"%s\""), projname);
break;
default:
if (error <= 0) {
"project \"%s\""), projname);
return;
}
/*
* If we have a stopped target process it may be in
* getprojbyname()'s execution path which would make it unsafe
* to access the project table, so only do that if the caller
* hasn't provided a cached version of the project structure.
*/
"assignment failed for project \"%s\" "
"attribute %d"),
if (kv_array)
return;
}
"assignment failed for project \"%s\""),
}
}