#include "pathnames.h"
#include "env.h"
#include "closestream.h"
+#include "strv.h"
#include "strutils.h"
#include "ttyutils.h"
#include "pwdutils.h"
+#include "optutils.h"
#include "logindefs.h"
#include "su-common.h"
pid_t child; /* fork() baby */
int childstatus; /* wait() status */
+ char **env_whitelist_names; /* environment whitelist */
+ char **env_whitelist_vals;
+
struct sigaction oldact[SIGNALS_IDX_COUNT]; /* original sigactions indexed by SIG*_IDX */
#ifdef USE_PTY
static void init_tty(struct su_context *su)
{
su->isterm = isatty(STDIN_FILENO) ? 1 : 0;
- DBG(TTY, ul_debug("initilize [is-term=%s]", su->isterm ? "true" : "false"));
+ DBG(TTY, ul_debug("initialize [is-term=%s]", su->isterm ? "true" : "false"));
if (su->isterm)
get_terminal_name(NULL, &su->tty_name, &su->tty_number);
}
if (su->isterm) {
DBG(PTY, ul_debug("create for terminal"));
- struct winsize win;
/* original setting of the current terminal */
if (tcgetattr(STDIN_FILENO, &su->stdin_attrs) != 0)
err(EXIT_FAILURE, _("failed to get terminal attributes"));
-
- /* reuse the current terminal setting for slave */
- slave_attrs = su->stdin_attrs;
- cfmakeraw(&slave_attrs);
-
ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&su->win);
-
/* create master+slave */
- rc = openpty(&su->pty_master, &su->pty_slave, NULL, &slave_attrs, &win);
+ rc = openpty(&su->pty_master, &su->pty_slave, NULL, &su->stdin_attrs, &su->win);
+ /* set the current terminal to raw mode; pty_cleanup() reverses this change on exit */
+ slave_attrs = su->stdin_attrs;
+ cfmakeraw(&slave_attrs);
+ slave_attrs.c_lflag &= ~ECHO;
+ tcsetattr(STDIN_FILENO, TCSANOW, &slave_attrs);
} else {
DBG(PTY, ul_debug("create for non-terminal"));
rc = openpty(&su->pty_master, &su->pty_slave, NULL, NULL, NULL);
- /* set slave attributes */
if (!rc) {
tcgetattr(su->pty_slave, &slave_attrs);
- cfmakeraw(&slave_attrs);
+ slave_attrs.c_lflag &= ~ECHO;
tcsetattr(su->pty_slave, TCSANOW, &slave_attrs);
}
}
static void pty_cleanup(struct su_context *su)
{
- if (su->pty_master == -1)
+ struct termios rtt;
+
+ if (su->pty_master == -1 || !su->isterm)
return;
DBG(PTY, ul_debug("cleanup"));
- if (su->isterm)
- tcsetattr(STDIN_FILENO, TCSADRAIN, &su->stdin_attrs);
+ rtt = su->stdin_attrs;
+ tcsetattr(STDIN_FILENO, TCSADRAIN, &rtt);
}
static int write_output(char *obuf, ssize_t bytes)
/* The child terminated or stopped. Note that we ignore SIGCONT
* here, because stop/cont semantic is handled by wait_for_child() */
- if (info.ssi_code == CLD_EXITED || info.ssi_status == SIGSTOP)
+ if (info.ssi_code == CLD_EXITED
+ || info.ssi_code == CLD_KILLED
+ || info.ssi_code == CLD_DUMPED
+ || info.ssi_status == SIGSTOP)
wait_for_child(su);
/* The child is dead, force poll() timeout. */
if (su->child == (pid_t) -1)
DBG(LOG, ul_debug("btmp logging"));
memset(&ut, 0, sizeof(ut));
- strncpy(ut.ut_user,
+ str2memcpy(ut.ut_user,
su->pwd && su->pwd->pw_name ? su->pwd->pw_name : "(unknown)",
sizeof(ut.ut_user));
if (su->tty_number)
- xstrncpy(ut.ut_id, su->tty_number, sizeof(ut.ut_id));
+ str2memcpy(ut.ut_id, su->tty_number, sizeof(ut.ut_id));
if (su->tty_name)
- xstrncpy(ut.ut_line, su->tty_name, sizeof(ut.ut_line));
+ str2memcpy(ut.ut_line, su->tty_name, sizeof(ut.ut_line));
gettimeofday(&tv, NULL);
ut.ut_tv.tv_sec = tv.tv_sec;
exit(status);
}
+/* Adds @name from the current environment to the whitelist. If @name is not
+ * set then nothing is added to the whitelist and returns 1.
+ */
+static int env_whitelist_add(struct su_context *su, const char *name)
+{
+ const char *env = getenv(name);
+
+ if (!env)
+ return 1;
+ if (strv_extend(&su->env_whitelist_names, name))
+ err_oom();
+ if (strv_extend(&su->env_whitelist_vals, env))
+ err_oom();
+ return 0;
+}
+
+static int env_whitelist_setenv(struct su_context *su, int overwrite)
+{
+ char **one;
+ size_t i = 0;
+ int rc;
+
+ STRV_FOREACH(one, su->env_whitelist_names) {
+ rc = setenv(*one, su->env_whitelist_vals[i], overwrite);
+ if (rc)
+ return rc;
+ i++;
+ }
+
+ return 0;
+}
+
+/* Creates (add to) whitelist from comma delimited string */
+static int env_whitelist_from_string(struct su_context *su, const char *str)
+{
+ char **all = strv_split(str, ",");
+ char **one;
+
+ if (!all) {
+ if (errno == ENOMEM)
+ err_oom();
+ return -EINVAL;
+ }
+
+ STRV_FOREACH(one, all)
+ env_whitelist_add(su, *one);
+ strv_free(all);
+ return 0;
+}
+
static void setenv_path(const struct passwd *pw)
{
int rc;
if (pw->pw_uid)
rc = logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH);
- else if ((rc = logindefs_setenv("PATH", "ENV_ROOTPATH", NULL)) != 0)
- rc = logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT);
+ else if ((rc = logindefs_setenv("PATH", "ENV_SUPATH", NULL)) != 0)
+ rc = logindefs_setenv("PATH", "ENV_ROOTPATH", _PATH_DEFPATH_ROOT);
if (rc)
err(EXIT_FAILURE, _("failed to set the PATH environment variable"));
DBG(MISC, ul_debug("modify environ[]"));
/* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
- * Unset all other environment variables.
+ *
+ * Unset all other environment variables, but follow
+ * --whitelist-environment if specified.
*/
if (su->simulate_login) {
- char *term = getenv("TERM");
- if (term)
- term = xstrdup(term);
-
- environ = xmalloc((6 + ! !term) * sizeof(char *));
- environ[0] = NULL;
- if (term) {
- xsetenv("TERM", term, 1);
- free(term);
- }
-
- xsetenv("HOME", pw->pw_dir, 1);
+ /* leave TERM unchanged */
+ env_whitelist_add(su, "TERM");
+
+ /* Note that original su(1) has allocated environ[] by malloc
+ * to the number of expected variables. This seems unnecessary
+ * optimization as libc later realloc(current_size+2) and for
+ * empty environ[] the curren_size is zero. It seems better to
+ * keep all logic around environment in glibc's hands.
+ * --kzak [Aug 2018]
+ */
+#ifdef HAVE_CLEARENV
+ clearenv();
+#else
+ environ = NULL;
+#endif
+ /* always reset */
if (shell)
xsetenv("SHELL", shell, 1);
+
+ setenv_path(pw);
+
+ xsetenv("HOME", pw->pw_dir, 1);
xsetenv("USER", pw->pw_name, 1);
xsetenv("LOGNAME", pw->pw_name, 1);
- setenv_path(pw);
+
+ /* apply all from whitelist, but no overwrite */
+ env_whitelist_setenv(su, 0);
/* Set HOME, SHELL, and (if not becoming a superuser) USER and LOGNAME.
*/
static void usage_common(void)
{
- fputs(_(" -m, -p, --preserve-environment do not reset environment variables\n"), stdout);
+ fputs(_(" -m, -p, --preserve-environment do not reset environment variables\n"), stdout);
+ fputs(_(" -w, --whitelist-environment <list> don't reset specified variables\n"), stdout);
+ fputs(USAGE_SEPARATOR, stdout);
+
fputs(_(" -g, --group <group> specify the primary group\n"), stdout);
fputs(_(" -G, --supp-group <group> specify a supplemental group\n"), stdout);
fputs(USAGE_SEPARATOR, stdout);
struct su_context *su = (struct su_context *) data;
DBG(MISC, ul_debug("loading logindefs"));
- logindefs_load_file(su->runuser ? _PATH_LOGINDEFS_RUNUSER : _PATH_LOGINDEFS_SU);
logindefs_load_file(_PATH_LOGINDEFS);
+ logindefs_load_file(su->runuser ? _PATH_LOGINDEFS_RUNUSER : _PATH_LOGINDEFS_SU);
}
/*
{"group", required_argument, NULL, 'g'},
{"supp-group", required_argument, NULL, 'G'},
{"user", required_argument, NULL, 'u'}, /* runuser only */
+ {"whitelist-environment", required_argument, NULL, 'w'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'V'},
{NULL, 0, NULL, 0}
};
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'm', 'w' }, /* preserve-environment, whitelist-environment */
+ { 'p', 'w' }, /* preserve-environment, whitelist-environment */
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
su->conv.appdata_ptr = (void *) su;
while ((optc =
- getopt_long(argc, argv, "c:fg:G:lmpPs:u:hV", longopts,
+ getopt_long(argc, argv, "c:fg:G:lmpPs:u:hVw:", longopts,
NULL)) != -1) {
+
+ err_exclusive_options(optc, longopts, excl, excl_st);
+
switch (optc) {
case 'c':
command = optarg;
su->change_environment = false;
break;
+ case 'w':
+ env_whitelist_from_string(su, optarg);
+ break;
+
case 'P':
#ifdef USE_PTY
su->pty = 1;