su: add control struct

* setup logindefs loader by function rather than by global pointer

* move basic booleans to the su_context struct

Signed-off-by: Karel Zak <kzak@redhat.com>
This commit is contained in:
Karel Zak 2016-10-13 14:22:52 +02:00
parent b1195aa07d
commit 832f5cd524
3 changed files with 124 additions and 140 deletions

View File

@ -48,7 +48,8 @@ struct item {
static struct item *list = NULL;
void (*logindefs_load_defaults)(void) = NULL;
static void (*logindefs_loader)(void *) = NULL;
static void *logindefs_loader_data = NULL;
void free_getlogindefs_data(void)
{
@ -144,10 +145,16 @@ void logindefs_load_file(const char *filename)
fclose(f);
}
void logindefs_set_loader(void (*loader)(void *data), void *data)
{
logindefs_loader = loader;
logindefs_loader_data = data;
}
static void load_defaults(void)
{
if (logindefs_load_defaults)
logindefs_load_defaults();
if (logindefs_loader)
logindefs_loader(logindefs_loader_data);
else
logindefs_load_file(_PATH_LOGINDEFS);
}

View File

@ -2,7 +2,7 @@
#define UTIL_LINUX_LOGINDEFS_H
extern void logindefs_load_file(const char *filename);
extern void (*logindefs_load_defaults)(void);
extern void logindefs_set_loader(void (*loader)(void *data), void *data);
extern int getlogindefs_bool(const char *name, int dflt);
extern unsigned long getlogindefs_num(const char *name, long dflt);
extern const char *getlogindefs_str(const char *name, const char *dflt);

View File

@ -80,33 +80,29 @@ enum {
EXIT_ENOENT = 127
};
static void run_shell(char const *, char const *, char **, size_t)
__attribute__ ((__noreturn__));
/*
* su/runuser control struct
*/
struct su_context {
pam_handle_t *pamh; /* PAM handler */
struct pam_conv conv; /* PAM conversation */
/* If true, pass the `-f' option to the subshell. */
static bool fast_startup;
unsigned int runuser :1, /* flase=su, true=runuser */
fast_startup :1, /* pass the `-f' option to the subshell. */
simulate_login :1, /* simulate a login instead of just starting a shell. */
change_environment :1, /* change some environment vars to indicate the user su'd to.*/
same_session :1, /* don't call setsid() with a command. */
suppress_pam_info:1, /* don't print PAM info messages (Last login, etc.). */
pam_has_session :1, /* PAM session opened */
pam_has_cred :1, /* PAM cred established */
restricted :1; /* false for root user */
};
/* If true, simulate a login instead of just starting a shell. */
static bool simulate_login;
/* If true, change some environment vars to indicate the user su'd to. */
static bool change_environment;
static void run_shell(struct su_context *, char const *, char const *, char **, size_t);
/* If true, then don't call setsid() with a command. */
static int same_session = 0;
/* SU_MODE_{RUNUSER,SU} */
static int su_mode;
/* Don't print PAM info messages (Last login, etc.). */
static int suppress_pam_info;
static bool _pam_session_opened;
static bool _pam_cred_established;
static sig_atomic_t volatile caught_signal = false;
static pam_handle_t *pamh = NULL;
static int restricted = 1; /* zero for root user */
static const struct passwd *
current_getpwuid(void)
@ -131,7 +127,7 @@ current_getpwuid(void)
if SUCCESSFUL is true, they gave the correct password, etc. */
static void
log_syslog(struct passwd const * const pw, const bool successful)
log_syslog(struct su_context *su, struct passwd const *pw, bool successful)
{
const char *new_user, *old_user, *tty;
@ -152,7 +148,7 @@ log_syslog(struct passwd const * const pw, const bool successful)
openlog(program_invocation_short_name, 0, LOG_AUTH);
syslog(LOG_NOTICE, "%s(to %s) %s on %s",
successful ? "" :
su_mode == RUNUSER_MODE ? "FAILED RUNUSER " : "FAILED SU ",
su->runuser ? "FAILED RUNUSER " : "FAILED SU ",
new_user, old_user, tty);
closelog();
}
@ -191,9 +187,12 @@ static int
su_pam_conv(int num_msg, const struct pam_message **msg,
struct pam_response **resp, void *appdata_ptr)
{
if (suppress_pam_info
struct su_context *su = (struct su_context *) appdata_ptr;
if (su->suppress_pam_info
&& num_msg == 1 && msg && msg[0]->msg_style == PAM_TEXT_INFO)
return PAM_SUCCESS;
#ifdef HAVE_SECURITY_PAM_MISC_H
return misc_conv(num_msg, msg, resp, appdata_ptr);
#elif defined(HAVE_SECURITY_OPENPAM_H)
@ -201,23 +200,18 @@ su_pam_conv(int num_msg, const struct pam_message **msg,
#endif
}
static struct pam_conv conv = {
su_pam_conv,
NULL
};
static void
cleanup_pam(const int retcode)
cleanup_pam(struct su_context *su, int retcode)
{
const int saved_errno = errno;
if (_pam_session_opened)
pam_close_session(pamh, 0);
if (su->pam_has_session)
pam_close_session(su->pamh, 0);
if (_pam_cred_established)
pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT);
if (su->pam_has_cred)
pam_setcred(su->pamh, PAM_DELETE_CRED | PAM_SILENT);
pam_end(pamh, retcode);
pam_end(su->pamh, retcode);
errno = saved_errno;
}
@ -231,12 +225,12 @@ su_catch_sig(int sig)
/* Export env variables declared by PAM modules. */
static void
export_pamenv(void)
export_pamenv(struct su_context *su)
{
char **env;
/* This is a copy but don't care to free as we exec later anyways. */
env = pam_getenvlist(pamh);
env = pam_getenvlist(su->pamh);
while (env && *env) {
if (putenv(*env) != 0)
err(EXIT_FAILURE, NULL);
@ -245,7 +239,7 @@ export_pamenv(void)
}
static void
create_watching_parent(void)
create_watching_parent(struct su_context *su)
{
pid_t child;
sigset_t ourset;
@ -253,19 +247,19 @@ create_watching_parent(void)
int status = 0;
int retval;
retval = pam_open_session(pamh, 0);
retval = pam_open_session(su->pamh, 0);
if (is_pam_failure(retval)) {
cleanup_pam(retval);
cleanup_pam(su, retval);
errx(EXIT_FAILURE, _("cannot open session: %s"),
pam_strerror(pamh, retval));
pam_strerror(su->pamh, retval));
} else
_pam_session_opened = 1;
su->pam_has_session = 1;
memset(oldact, 0, sizeof(oldact));
child = fork();
if (child == (pid_t) - 1) {
cleanup_pam(PAM_ABORT);
cleanup_pam(su, PAM_ABORT);
err(EXIT_FAILURE, _("cannot create child process"));
}
@ -291,7 +285,7 @@ create_watching_parent(void)
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
sigemptyset(&ourset);
if (!same_session) {
if (!su->same_session) {
if (sigaddset(&ourset, SIGINT)
|| sigaddset(&ourset, SIGQUIT)) {
warn(_("cannot set signal handler"));
@ -307,7 +301,7 @@ create_watching_parent(void)
warn(_("cannot set signal handler"));
caught_signal = true;
}
if (!caught_signal && !same_session
if (!caught_signal && !su->same_session
&& (sigaction(SIGINT, &action, &oldact[1])
|| sigaction(SIGQUIT, &action, &oldact[2]))) {
warn(_("cannot set signal handler"));
@ -347,7 +341,7 @@ create_watching_parent(void)
kill(child, SIGTERM);
}
cleanup_pam(PAM_SUCCESS);
cleanup_pam(su, PAM_SUCCESS);
if (caught_signal) {
sleep(2);
@ -382,27 +376,17 @@ create_watching_parent(void)
}
static void
authenticate(const struct passwd *pw)
authenticate(struct su_context *su, const struct passwd *pw)
{
const struct passwd *lpw = NULL;
const char *cp, *srvname = NULL;
int retval;
switch (su_mode) {
case SU_MODE:
srvname = simulate_login ? PAM_SRVNAME_SU_L : PAM_SRVNAME_SU;
break;
case RUNUSER_MODE:
srvname =
simulate_login ? PAM_SRVNAME_RUNUSER_L :
PAM_SRVNAME_RUNUSER;
break;
default:
abort();
break;
}
srvname = su->runuser ?
(su->simulate_login ? PAM_SRVNAME_RUNUSER_L : PAM_SRVNAME_RUNUSER) :
(su->simulate_login ? PAM_SRVNAME_SU_L : PAM_SRVNAME_SU);
retval = pam_start(srvname, pw->pw_name, &conv, &pamh);
retval = pam_start(srvname, pw->pw_name, &su->conv, &su->pamh);
if (is_pam_failure(retval))
goto done;
@ -413,51 +397,49 @@ authenticate(const struct passwd *pw)
tty = cp + 5;
else
tty = cp;
retval = pam_set_item(pamh, PAM_TTY, tty);
retval = pam_set_item(su->pamh, PAM_TTY, tty);
if (is_pam_failure(retval))
goto done;
}
lpw = current_getpwuid();
if (lpw && lpw->pw_name) {
retval =
pam_set_item(pamh, PAM_RUSER, (const void *)lpw->pw_name);
retval = pam_set_item(su->pamh, PAM_RUSER, (const void *)lpw->pw_name);
if (is_pam_failure(retval))
goto done;
}
if (su_mode == RUNUSER_MODE) {
if (su->runuser) {
/*
* This is the only difference between runuser(1) and su(1). The command
* runuser(1) does not required authentication, because user is root.
*/
if (restricted)
errx(EXIT_FAILURE,
_("may not be used by non-root users"));
if (su->restricted)
errx(EXIT_FAILURE, _("may not be used by non-root users"));
return;
}
retval = pam_authenticate(pamh, 0);
retval = pam_authenticate(su->pamh, 0);
if (is_pam_failure(retval))
goto done;
retval = pam_acct_mgmt(pamh, 0);
retval = pam_acct_mgmt(su->pamh, 0);
if (retval == PAM_NEW_AUTHTOK_REQD) {
/* Password has expired. Offer option to change it. */
retval = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
retval = pam_chauthtok(su->pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
}
done:
log_syslog(pw, !is_pam_failure(retval));
log_syslog(su, pw, !is_pam_failure(retval));
if (is_pam_failure(retval)) {
const char *msg;
log_btmp(pw);
msg = pam_strerror(pamh, retval);
pam_end(pamh, retval);
msg = pam_strerror(su->pamh, retval);
pam_end(su->pamh, retval);
sleep(getlogindefs_num("FAIL_DELAY", 1));
errx(EXIT_FAILURE, "%s", msg ? msg : _("incorrect password"));
}
@ -482,9 +464,9 @@ set_path(const struct passwd * const pw)
the value for the SHELL environment variable. */
static void
modify_environment (const struct passwd * const pw, const char * const shell)
modify_environment(struct su_context *su, const struct passwd *pw, const char *shell)
{
if (simulate_login) {
if (su->simulate_login) {
/* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
Unset all other environment variables. */
char *term = getenv("TERM");
@ -505,7 +487,7 @@ modify_environment (const struct passwd * const pw, const char * const shell)
} else {
/* Set HOME, SHELL, and (if not becoming a superuser)
USER and LOGNAME. */
if (change_environment) {
if (su->change_environment) {
xsetenv("HOME", pw->pw_dir, 1);
if (shell)
xsetenv("SHELL", shell, 1);
@ -519,13 +501,13 @@ modify_environment (const struct passwd * const pw, const char * const shell)
}
}
export_pamenv();
export_pamenv(su);
}
/* Become the user and group(s) specified by PW. */
static void
init_groups (const struct passwd * const pw, const gid_t * const groups, const size_t num_groups)
init_groups(struct su_context *su, const struct passwd *pw, gid_t * groups, size_t num_groups)
{
int retval;
@ -537,16 +519,16 @@ init_groups (const struct passwd * const pw, const gid_t * const groups, const s
retval = initgroups(pw->pw_name, pw->pw_gid);
if (retval == -1) {
cleanup_pam(PAM_ABORT);
cleanup_pam(su, PAM_ABORT);
err(EXIT_FAILURE, _("cannot set groups"));
}
endgrent();
retval = pam_setcred(pamh, PAM_ESTABLISH_CRED);
retval = pam_setcred(su->pamh, PAM_ESTABLISH_CRED);
if (is_pam_failure(retval))
errx(EXIT_FAILURE, "%s", pam_strerror(pamh, retval));
errx(EXIT_FAILURE, "%s", pam_strerror(su->pamh, retval));
else
_pam_cred_established = 1;
su->pam_has_cred = 1;
}
static void
@ -564,15 +546,16 @@ change_identity (const struct passwd * const pw)
are N_ADDITIONAL_ARGS extra arguments. */
static void
run_shell (char const * const shell, char const * const command, char ** const additional_args,
const size_t n_additional_args)
run_shell(struct su_context *su,
char const *shell, char const *command, char **additional_args,
size_t n_additional_args)
{
size_t n_args =
1 + fast_startup + 2 * ! !command + n_additional_args + 1;
1 + su->fast_startup + 2 * ! !command + n_additional_args + 1;
char const **args = xcalloc(n_args, sizeof *args);
size_t argno = 1;
if (simulate_login) {
if (su->simulate_login) {
char *arg0;
char *shell_basename;
@ -583,7 +566,7 @@ run_shell (char const * const shell, char const * const command, char ** const a
args[0] = arg0;
} else
args[0] = basename(shell);
if (fast_startup)
if (su->fast_startup)
args[argno++] = "-f";
if (command) {
args[argno++] = "-c";
@ -620,10 +603,9 @@ restricted_shell (const char * const shell)
return true;
}
static void __attribute__ ((__noreturn__)) usage(int status)
static void __attribute__ ((__noreturn__)) usage(int status, int mode)
{
if (su_mode == RUNUSER_MODE) {
if (mode == RUNUSER_MODE) {
fputs(USAGE_HEADER, stdout);
printf(_(" %s [options] -u <user> <command>\n"),
program_invocation_short_name);
@ -681,22 +663,15 @@ static void __attribute__ ((__noreturn__)) usage(int status)
fputs(USAGE_SEPARATOR, stdout);
printf(USAGE_HELP_OPTIONS(22));
printf(USAGE_MAN_TAIL(su_mode == SU_MODE ? "su(1)" : "runuser(1)"));
printf(USAGE_MAN_TAIL(mode == SU_MODE ? "su(1)" : "runuser(1)"));
exit(status);
}
static void
load_config(void)
static void load_config(void *data)
{
switch (su_mode) {
case SU_MODE:
logindefs_load_file(_PATH_LOGINDEFS_SU);
break;
case RUNUSER_MODE:
logindefs_load_file(_PATH_LOGINDEFS_RUNUSER);
break;
}
struct su_context *su = (struct su_context *) data;
logindefs_load_file(su->runuser ? _PATH_LOGINDEFS_RUNUSER : _PATH_LOGINDEFS_SU);
logindefs_load_file(_PATH_LOGINDEFS);
}
@ -739,6 +714,12 @@ add_supp_group(const char *name, gid_t ** groups, size_t * ngroups)
int
su_main(int argc, char **argv, int mode)
{
struct su_context _su = {
.conv = { su_pam_conv, NULL },
.runuser = (mode == RUNUSER_MODE ? 1 : 0),
.change_environment = 1
}, *su = &_su;
int optc;
const char *new_user = DEFAULT_USER, *runuser_user = NULL;
char *command = NULL;
@ -773,10 +754,7 @@ su_main(int argc, char **argv, int mode)
textdomain(PACKAGE);
atexit(close_stdout);
su_mode = mode;
fast_startup = false;
simulate_login = false;
change_environment = true;
su->conv.appdata_ptr = (void *) su;
while ((optc =
getopt_long(argc, argv, "c:fg:G:lmps:u:hV", longopts,
@ -792,7 +770,7 @@ su_main(int argc, char **argv, int mode)
break;
case 'f':
fast_startup = true;
su->fast_startup = true;
break;
case 'g':
@ -806,12 +784,12 @@ su_main(int argc, char **argv, int mode)
break;
case 'l':
simulate_login = true;
su->simulate_login = true;
break;
case 'm':
case 'p':
change_environment = false;
su->change_environment = false;
break;
case 's':
@ -819,42 +797,42 @@ su_main(int argc, char **argv, int mode)
break;
case 'u':
if (su_mode != RUNUSER_MODE)
usage(EXIT_FAILURE);
if (!su->runuser)
usage(mode, EXIT_FAILURE);
runuser_user = optarg;
break;
case 'h':
usage(0);
usage(mode, 0);
case 'V':
printf(UTIL_LINUX_VERSION);
exit(EXIT_SUCCESS);
default:
usage(EXIT_FAILURE);
usage(mode, EXIT_FAILURE);
}
}
restricted = evaluate_uid();
su->restricted = evaluate_uid();
if (optind < argc && !strcmp(argv[optind], "-")) {
simulate_login = true;
su->simulate_login = true;
++optind;
}
if (simulate_login && !change_environment) {
if (su->simulate_login && !su->change_environment) {
warnx(_
("ignoring --preserve-environment, it's mutually exclusive with --login"));
change_environment = true;
su->change_environment = true;
}
switch (su_mode) {
switch (mode) {
case RUNUSER_MODE:
if (runuser_user) {
/* runuser -u <user> <command> */
new_user = runuser_user;
if (shell || fast_startup || command || simulate_login) {
if (shell || su->fast_startup || command || su->simulate_login) {
errx(EXIT_FAILURE,
_
("options --{shell,fast,command,session-command,login} and "
@ -875,11 +853,11 @@ su_main(int argc, char **argv, int mode)
break;
}
if ((use_supp || use_gid) && restricted)
if ((use_supp || use_gid) && su->restricted)
errx(EXIT_FAILURE,
_("only root can specify alternative groups"));
logindefs_load_defaults = load_config;
logindefs_set_loader(load_config, (void *) su);
pw = getpwnam(new_user);
if (!(pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0]
@ -906,16 +884,16 @@ su_main(int argc, char **argv, int mode)
else if (use_gid)
pw->pw_gid = gid;
authenticate(pw);
authenticate(su, pw);
if (request_same_session || !command || !pw->pw_uid)
same_session = 1;
su->same_session = 1;
/* initialize shell variable only if "-u <user>" not specified */
if (runuser_user) {
shell = NULL;
} else {
if (!shell && !change_environment)
if (!shell && !su->change_environment)
shell = getenv("SHELL");
if (shell && getuid() != 0 && restricted_shell(pw->pw_shell)) {
/* The user being su'd to has a nonstandard shell, and so is
@ -928,30 +906,29 @@ su_main(int argc, char **argv, int mode)
shell = xstrdup(shell ? shell : pw->pw_shell);
}
init_groups(pw, groups, ngroups);
init_groups(su, pw, groups, ngroups);
if (!simulate_login || command)
suppress_pam_info = 1; /* don't print PAM info messages */
if (!su->simulate_login || command)
su->suppress_pam_info = 1; /* don't print PAM info messages */
create_watching_parent();
create_watching_parent(su);
/* Now we're in the child. */
change_identity(pw);
if (!same_session)
if (!su->same_session)
setsid();
/* Set environment after pam_open_session, which may put KRB5CCNAME
into the pam_env, etc. */
modify_environment(pw, shell);
modify_environment(su, pw, shell);
if (simulate_login && chdir(pw->pw_dir) != 0)
if (su->simulate_login && chdir(pw->pw_dir) != 0)
warn(_("warning: cannot change directory to %s"), pw->pw_dir);
if (shell)
run_shell(shell, command, argv + optind, max(0, argc - optind));
else {
execvp(argv[optind], &argv[optind]);
err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]);
}
run_shell(su, shell, command, argv + optind, max(0, argc - optind));
execvp(argv[optind], &argv[optind]);
err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]);
}