#include <sys/types.h>
#include <sys/task.h>
#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 <sys/varargs.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <priv_utils.h>
#include "utils.h"
#define OPTIONS_STRING "Fc:lp:v"
#define NENV 8
#define ENVSIZE 255
#define PATH "PATH=/usr/bin"
#define SUPATH "PATH=/usr/sbin:/usr/bin"
#define SHELL "/usr/bin/sh"
#define SHELL2 "/sbin/sh"
#define TIMEZONEFILE "/etc/default/init"
#define LOGINFILE "/etc/default/login"
#define GLOBAL_ERR_SZ 1024
#define GRAB_RETRY_MAX 100
static const char *pname;
extern char **environ;
static char *supath = SUPATH;
static char *path = PATH;
static char global_error[GLOBAL_ERR_SZ];
static int verbose = 0;
static priv_set_t *nset;
extern projid_t setproject_proc(const char *, const char *, int, pid_t,
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 *);
static int set_ids(struct ps_prochandle *, struct project *,
struct passwd *);
static struct passwd *match_user(uid_t, char *, int);
static void setproject_err(char *, char *, int, struct project *);
static void
usage(void)
{
(void) fprintf(stderr, gettext("usage: \n\t%s [-v] [-p project] "
"[-c pid | [-Fl] [command [args ...]]]\n"), pname);
exit(2);
}
int
main(int argc, char *argv[])
{
int c;
struct passwd *pw;
char *projname = NULL;
uid_t uid;
int login_flag = 0;
int finalize_flag = TASK_NORMAL;
int newproj_flag = 0;
taskid_t taskid;
char *shell;
char *env[NENV];
char **targs;
char *filename, *procname = NULL;
int error;
nset = setproject_initpriv();
if (nset == NULL)
die(gettext("privilege initialization failed\n"));
pname = getpname(argv[0]);
while ((c = getopt(argc, argv, OPTIONS_STRING)) != EOF) {
switch (c) {
case 'v':
verbose = 1;
break;
case 'p':
newproj_flag = 1;
projname = optarg;
break;
case 'F':
finalize_flag = TASK_FINAL;
break;
case 'l':
login_flag++;
break;
case 'c':
procname = optarg;
break;
case '?':
default:
usage();
}
}
if ((procname != NULL) &&
(finalize_flag == TASK_FINAL || login_flag || optind < argc))
usage();
if (procname != NULL) {
return (update_running_proc(newproj_flag, procname, projname));
}
uid = getuid();
if ((pw = match_user(uid, projname, 1)) == NULL) {
die("%s\n", global_error);
}
(void) __priv_bracket(PRIV_ON);
if (projname == NULL) {
if (settaskid(getprojid(), finalize_flag) == -1)
if (errno == EAGAIN)
die(gettext("resource control limit has been "
"reached"));
else
die(gettext("settaskid failed"));
} else {
if ((error = setproject(projname,
pw->pw_name, finalize_flag)) != 0) {
setproject_err(pw->pw_name, projname, error, NULL);
if (error < 0)
die("%s\n", global_error);
else
warn("%s\n", global_error);
}
}
__priv_relinquish();
taskid = gettaskid();
if (verbose)
(void) fprintf(stderr, "%d\n", (int)taskid);
if (strcmp(pw->pw_shell, "") == 0) {
if (access(SHELL, X_OK) == 0)
pw->pw_shell = SHELL;
else
pw->pw_shell = SHELL2;
}
if (login_flag) {
char *cur_tz = getenv("TZ");
char *cur_term = getenv("TERM");
char **envnext;
size_t len_home = strlen(pw->pw_dir) + strlen("HOME=") + 1;
size_t len_logname = strlen(pw->pw_name) + strlen("LOGNAME=") +
1;
size_t len_shell = strlen(pw->pw_shell) + strlen("SHELL=") + 1;
size_t len_mail = strlen(pw->pw_name) +
strlen("MAIL=/var/mail/") + 1;
size_t len_tz;
size_t len_term;
char *env_home = safe_malloc(len_home);
char *env_logname = safe_malloc(len_logname);
char *env_shell = safe_malloc(len_shell);
char *env_mail = safe_malloc(len_mail);
char *env_tz;
char *env_term;
(void) snprintf(env_home, len_home, "HOME=%s", pw->pw_dir);
(void) snprintf(env_logname, len_logname, "LOGNAME=%s",
pw->pw_name);
(void) snprintf(env_shell, len_shell, "SHELL=%s", pw->pw_shell);
(void) snprintf(env_mail, len_mail, "MAIL=/var/mail/%s",
pw->pw_name);
env[0] = env_home;
env[1] = env_logname;
env[2] = (pw->pw_uid == 0 ? supath : path);
env[3] = env_shell;
env[4] = env_mail;
env[5] = NULL;
env[6] = NULL;
env[7] = NULL;
envnext = (char **)&env[5];
if (cur_term != NULL) {
len_term = strlen(cur_term) + strlen("TERM=") + 1;
env_term = safe_malloc(len_term);
(void) snprintf(env_term, len_term, "TERM=%s",
cur_term);
*envnext = env_term;
envnext++;
}
if (cur_tz != NULL) {
len_tz = strlen(cur_tz) + strlen("TZ=") + 1;
env_tz = safe_malloc(len_tz);
(void) snprintf(env_tz, len_tz, "TZ=%s", cur_tz);
*envnext = env_tz;
} else {
if ((env_tz = getdefault(TIMEZONEFILE, "TZ=",
"TZ=")) != NULL)
*envnext = env_tz;
else {
env_tz = getdefault(LOGINFILE, "TIMEZONE=",
"TZ=");
*envnext = env_tz;
}
}
environ = (char **)&env[0];
shell = safe_malloc(PATH_MAX);
(void) snprintf(shell, PATH_MAX, "-%s", basename(pw->pw_shell));
} else {
shell = basename(pw->pw_shell);
}
if (optind >= argc) {
targs = alloca(2 * sizeof (char *));
filename = pw->pw_shell;
targs[0] = shell;
targs[1] = NULL;
} else {
targs = &argv[optind];
filename = targs[0];
}
if (execvp(filename, targs) == -1)
die(gettext("exec of %s failed"), targs[0]);
return (1);
}
static int
update_running_proc(int newproj_flag, char *procname, char *projname)
{
struct ps_prochandle *p;
prcred_t original_prcred, current_prcred;
projid_t prprojid;
taskid_t taskid;
int error = 0, gret;
struct project project;
char prbuf[PROJECT_BUFSZ];
struct passwd *passwd_entry;
int grab_retry_count = 0;
(void) sigignore(SIGHUP);
(void) sigignore(SIGINT);
(void) sigignore(SIGQUIT);
(void) sigignore(SIGTERM);
(void) fflush(stdout);
if ((p = proc_arg_grab(procname, PR_ARG_PIDS, 0, &gret)) == NULL) {
warn(gettext("failed to grab for process %s: %s\n"),
procname, Pgrab_error(gret));
return (1);
}
if (Pcreate_agent(p) != 0) {
Prelease(p, 0);
warn(gettext("cannot control process %s\n"), procname);
return (1);
}
pgrab_retry:
if (Pcred(p, &original_prcred, 0) != 0) {
preserve_error(gettext("cannot get process credentials %s\n"),
procname);
error = 1;
}
if ((prprojid = pr_getprojid(p)) == -1) {
preserve_error(gettext("cannot get process project id %s\n"),
procname);
error = 1;
}
Pdestroy_agent(p);
Prelease(p, 0);
if (error) {
warn("%s\n", global_error);
return (1);
}
if (newproj_flag == 0) {
if (getprojbyid(prprojid, &project, &prbuf,
PROJECT_BUFSZ) == NULL) {
warn(gettext("unable to get project name "
"for projid %d"), prprojid);
return (1);
}
projname = project.pj_name;
} else {
if (getprojbyname(projname, &project, &prbuf,
PROJECT_BUFSZ) == NULL) {
warn(gettext("unknown project \"%s\"\n"), projname);
return (1);
}
}
if ((passwd_entry = match_user(original_prcred.pr_ruid,
projname, 0)) == NULL) {
warn("%s\n", global_error);
return (1);
}
if ((p = proc_arg_grab(procname, PR_ARG_PIDS, 0, &gret)) == NULL) {
warn(gettext("failed to grab for process %s: %s\n"),
procname, Pgrab_error(gret));
return (1);
}
if (Pcreate_agent(p) != 0) {
Prelease(p, 0);
warn(gettext("cannot control process %s\n"), procname);
return (1);
}
if (getuid() != 0) {
if (Pcred(p, ¤t_prcred, 0) != 0) {
Pdestroy_agent(p);
Prelease(p, 0);
warn(gettext("can't get process credentials %s\n"),
procname);
return (1);
}
if (original_prcred.pr_ruid != current_prcred.pr_ruid) {
if (grab_retry_count++ < GRAB_RETRY_MAX)
goto pgrab_retry;
warn(gettext("process consistently changed its "
"user id %s\n"), procname);
return (1);
}
}
error = set_ids(p, &project, passwd_entry);
if (verbose)
taskid = pr_gettaskid(p);
Pdestroy_agent(p);
Prelease(p, 0);
if (error) {
warn("%s\n", global_error);
if (error < 0)
return (1);
}
if (verbose)
(void) fprintf(stderr, "%d\n", (int)taskid);
return (0);
}
static int
set_ids(struct ps_prochandle *p, struct project *project,
struct passwd *passwd_entry)
{
int be_su = 0;
prcred_t old_prcred;
int error;
prpriv_t *old_prpriv, *new_prpriv;
size_t prsz = sizeof (prpriv_t);
priv_set_t *eset, *pset;
int ind;
if (Pcred(p, &old_prcred, 0) != 0) {
preserve_error(gettext("can't get process credentials"));
return (1);
}
old_prpriv = proc_get_priv(Pstatus(p)->pr_pid);
if (old_prpriv == NULL) {
preserve_error(gettext("can't get process privileges"));
return (1);
}
prsz = PRIV_PRPRIV_SIZE(old_prpriv);
new_prpriv = malloc(prsz);
if (new_prpriv == NULL) {
preserve_error(gettext("can't allocate memory"));
proc_free_priv(old_prpriv);
return (1);
}
(void) memcpy(new_prpriv, old_prpriv, prsz);
ind = priv_getsetbyname(PRIV_EFFECTIVE);
eset = (priv_set_t *)&new_prpriv->pr_sets[new_prpriv->pr_setsize * ind];
ind = priv_getsetbyname(PRIV_PERMITTED);
pset = (priv_set_t *)&new_prpriv->pr_sets[new_prpriv->pr_setsize * ind];
if (!priv_issubset(nset, eset)) {
be_su = 1;
priv_union(nset, eset);
priv_union(nset, pset);
if (Psetflags(p, PR_KLC) != 0) {
preserve_error(gettext("cannot set process "
"privileges"));
(void) Punsetflags(p, PR_KLC);
free(new_prpriv);
proc_free_priv(old_prpriv);
return (1);
}
(void) __priv_bracket(PRIV_ON);
if (Psetpriv(p, new_prpriv) != 0) {
(void) __priv_bracket(PRIV_OFF);
preserve_error(gettext("cannot set process "
"privileges"));
(void) Punsetflags(p, PR_KLC);
free(new_prpriv);
proc_free_priv(old_prpriv);
return (1);
}
(void) __priv_bracket(PRIV_OFF);
}
(void) __priv_bracket(PRIV_ON);
if ((error = setproject_proc(project->pj_name,
passwd_entry->pw_name, 0, Pstatus(p)->pr_pid, p, project)) != 0) {
setproject_err(passwd_entry->pw_name, project->pj_name,
error, project);
}
(void) __priv_bracket(PRIV_OFF);
if (be_su) {
(void) __priv_bracket(PRIV_ON);
if (Psetpriv(p, old_prpriv) != 0) {
(void) __priv_bracket(PRIV_OFF);
Pdestroy_agent(p);
die(gettext("cannot relinquish superuser credentials "
"for pid %d. The process was killed."),
Pstatus(p)->pr_pid);
}
(void) __priv_bracket(PRIV_OFF);
if (Punsetflags(p, PR_KLC) != 0)
preserve_error(gettext("error relinquishing "
"credentials. Process %d will be killed."),
Pstatus(p)->pr_pid);
}
free(new_prpriv);
proc_free_priv(old_prpriv);
return (error);
}
void
preserve_error(const char *format, ...)
{
va_list alist;
va_start(alist, format);
(void) vsnprintf(global_error, GLOBAL_ERR_SZ-1, format, alist);
va_end(alist);
}
static struct passwd *
match_user(uid_t uid, char *projname, int is_my_uid)
{
char prbuf[PROJECT_BUFSZ], username[LOGNAME_MAX+1];
struct project prj;
char *tmp_name;
struct passwd *pw = NULL;
if (is_my_uid) {
if ((tmp_name = getlogin()) == NULL ||
(pw = getpwnam(tmp_name)) == NULL || (pw->pw_uid != uid) ||
(pw->pw_name == NULL))
pw = NULL;
}
if (pw == NULL) {
if (((pw = getpwuid(uid)) == NULL) || pw->pw_name == NULL) {
preserve_error(gettext("cannot find username "
"for uid %d"), uid);
return (NULL);
}
}
if (projname == NULL || getuid() == (uid_t)0)
return (pw);
(void) strlcpy(username, pw->pw_name, sizeof (username));
if (inproj(username, projname, prbuf, PROJECT_BUFSZ) == 0) {
char **u;
tmp_name = NULL;
if (getprojbyname(projname, &prj, prbuf,
PROJECT_BUFSZ) == NULL) {
preserve_error(gettext("unknown project \"%s\""),
projname);
return (NULL);
}
for (u = prj.pj_users; *u; u++) {
if ((pw = getpwnam(*u)) == NULL)
continue;
if (pw->pw_uid == uid) {
tmp_name = pw->pw_name;
break;
}
}
if (tmp_name == NULL) {
preserve_error(gettext("user \"%s\" is not a member of "
"project \"%s\""), username, projname);
return (NULL);
}
}
return (pw);
}
void
setproject_err(char *username, char *projname, int error, struct project *proj)
{
kva_t *kv_array = NULL;
char prbuf[PROJECT_BUFSZ];
struct project local_proj;
switch (error) {
case SETPROJ_ERR_TASK:
if (errno == EAGAIN)
preserve_error(gettext("resource control limit has "
"been reached"));
else if (errno == ESRCH)
preserve_error(gettext("user \"%s\" is not a member of "
"project \"%s\""), username, projname);
else if (errno == EACCES)
preserve_error(gettext("the invoking task is final"));
else
preserve_error(
gettext("could not join project \"%s\""),
projname);
break;
case SETPROJ_ERR_POOL:
if (errno == EACCES)
preserve_error(gettext("no resource pool accepting "
"default bindings exists for project \"%s\""),
projname);
else if (errno == ESRCH)
preserve_error(gettext("specified resource pool does "
"not exist for project \"%s\""), projname);
else
preserve_error(gettext("could not bind to default "
"resource pool for project \"%s\""), projname);
break;
default:
if (error <= 0) {
preserve_error(gettext("setproject failed for "
"project \"%s\""), projname);
return;
}
if (proj == NULL)
proj = getprojbyname(projname, &local_proj, prbuf,
PROJECT_BUFSZ);
if (proj == NULL || (kv_array = _str2kva(proj->pj_attr,
KV_ASSIGN, KV_DELIMITER)) == NULL ||
kv_array->length < error) {
preserve_error(gettext("warning, resource control "
"assignment failed for project \"%s\" "
"attribute %d"),
projname, error);
if (kv_array)
_kva_free(kv_array);
return;
}
preserve_error(gettext("warning, %s resource control "
"assignment failed for project \"%s\""),
kv_array->data[error - 1].key, projname);
_kva_free(kv_array);
}
}