From e61d2011d1304fedddf60e4ee21770d34d81d939 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 9 Apr 2025 00:55:27 +0200 Subject: [PATCH] run0: introduce --via-shell for invoking target user's shell, and -i shortcut -i/--login has exact sudo semantics. But we only document the short option and advertise expressly specifying --via-shell --chdir='~' otherwise. --- man/run0.xml | 27 +++++++++++++++++++++--- src/run/run.c | 57 ++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/man/run0.xml b/man/run0.xml index 262fa45ddc1..d853e2d4633 100644 --- a/man/run0.xml +++ b/man/run0.xml @@ -167,6 +167,24 @@ + + + + Invokes the target user's login shell and runs the specified command (if any) via it. + + + + + + + + + Shortcut for . + + + + + @@ -290,9 +308,12 @@ All command line arguments after the first non-option argument become part of the command line of the launched process. If no command line is specified an interactive shell is invoked. The shell to - invoke may be controlled via and currently defaults to the - originating user's shell (i.e. not the target user's!) if operating locally, or - /bin/sh when operating with . + invoke may be controlled through - when specified the target user's shell + is used - or . By default, the originating user's shell + is executed if operating locally, or /bin/sh when operating with . + + Note that unlike sudo, run0 always spawns shells with login shell + semantics, regardless of . diff --git a/src/run/run.c b/src/run/run.c index 4504c97df19..002874d282a 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -104,6 +104,7 @@ static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; static char *arg_shell_prompt_prefix = NULL; static int arg_lightweight = -1; static char *arg_area = NULL; +static bool arg_via_shell = false; STATIC_DESTRUCTOR_REGISTER(arg_description, freep); STATIC_DESTRUCTOR_REGISTER(arg_environment, strv_freep); @@ -215,6 +216,8 @@ static int help_sudo_mode(void) { " -g --group=GROUP Run as system group\n" " --nice=NICE Nice level\n" " -D --chdir=PATH Set working directory\n" + " --via-shell Invoke command via target user's login shell\n" + " -i Shortcut for --via-shell --chdir='~'\n" " --setenv=NAME[=VALUE] Set environment variable\n" " --background=COLOR Set ANSI color for background\n" " --pty Request allocation of a pseudo TTY for stdio\n" @@ -257,7 +260,7 @@ static int add_timer_property(const char *name, const char *val) { return 0; } -static char **make_login_shell_cmdline(const char *shell) { +static char** make_login_shell_cmdline(const char *shell) { _cleanup_free_ char *argv0 = NULL; assert(shell); @@ -826,6 +829,7 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { ARG_PIPE, ARG_SHELL_PROMPT_PREFIX, ARG_LIGHTWEIGHT, + ARG_VIA_SHELL, }; /* If invoked as "run0" binary, let's expose a more sudo-like interface. We add various extensions @@ -845,6 +849,8 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { { "group", required_argument, NULL, 'g' }, { "nice", required_argument, NULL, ARG_NICE }, { "chdir", required_argument, NULL, 'D' }, + { "via-shell", no_argument, NULL, ARG_VIA_SHELL }, + { "login", no_argument, NULL, 'i' }, /* compat with sudo, --via-shell + --chdir='~' */ { "setenv", required_argument, NULL, ARG_SETENV }, { "background", required_argument, NULL, ARG_BACKGROUND }, { "pty", no_argument, NULL, ARG_PTY }, @@ -864,7 +870,7 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ optind = 0; - while ((c = getopt_long(argc, argv, "+hVu:g:D:a:", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "+hVu:g:D:a:i", options, NULL)) >= 0) switch (c) { @@ -981,6 +987,16 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { break; + case 'i': + r = free_and_strdup_warn(&arg_working_directory, "~"); + if (r < 0) + return r; + + _fallthrough_; + case ARG_VIA_SHELL: + arg_via_shell = true; + break; + case '?': return -EINVAL; @@ -1029,9 +1045,11 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { arg_send_sighup = true; _cleanup_strv_free_ char **l = NULL; - if (argc > optind) + if (argc > optind) { l = strv_copy(argv + optind); - else { + if (!l) + return log_oom(); + } else if (!arg_via_shell) { const char *e; e = strv_env_get(arg_environment, "SHELL"); @@ -1056,9 +1074,19 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { } l = make_login_shell_cmdline(arg_exec_path); + if (!l) + return log_oom(); + } + + if (arg_via_shell) { + arg_exec_path = strdup(_PATH_BSHELL); + if (!arg_exec_path) + return log_oom(); + + r = strv_prepend(&l, "-sh"); + if (r < 0) + return log_oom(); } - if (!l) - return log_oom(); strv_free_and_replace(arg_cmdline, l); @@ -1271,10 +1299,8 @@ static int transient_kill_set_properties(sd_bus_message *m) { static int transient_service_set_properties(sd_bus_message *m, const char *pty_path, int pty_fd) { int r, send_term; /* tri-state */ - /* We disable environment expansion on the server side via ExecStartEx=:. - * ExecStartEx was added relatively recently (v243), and some bugs were fixed only later. - * So use that feature only if required. It will fail with older systemds. */ - bool use_ex_prop = !arg_expand_environment; + /* Use ExecStartEx if new exec flags are required. */ + bool use_ex_prop = !arg_expand_environment || arg_via_shell; assert(m); assert((!!pty_path) == (pty_fd >= 0)); @@ -1461,7 +1487,9 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p _cleanup_strv_free_ char **opts = NULL; r = exec_command_flags_to_strv( - (arg_expand_environment ? 0 : EXEC_COMMAND_NO_ENV_EXPAND)|(arg_ignore_failure ? EXEC_COMMAND_IGNORE_FAILURE : 0), + (arg_expand_environment ? 0 : EXEC_COMMAND_NO_ENV_EXPAND)| + (arg_ignore_failure ? EXEC_COMMAND_IGNORE_FAILURE : 0)| + (arg_via_shell ? EXEC_COMMAND_VIA_SHELL : 0), &opts); if (r < 0) return log_error_errno(r, "Failed to format execute flags: %m"); @@ -2821,7 +2849,12 @@ static int run(int argc, char* argv[]) { if (strv_isempty(arg_cmdline)) t = strdup(arg_unit); - else if (startswith(arg_cmdline[0], "-")) { + else if (arg_via_shell) { + if (arg_cmdline[1]) + t = quote_command_line(arg_cmdline + 1, SHELL_ESCAPE_EMPTY); + else + t = strjoin("LOGIN", arg_exec_user ? ": " : NULL, arg_exec_user); + } else if (startswith(arg_cmdline[0], "-")) { /* Drop the login shell marker from the command line when generating the description, * in order to minimize user confusion. */ _cleanup_strv_free_ char **l = strv_copy(arg_cmdline); -- 2.47.3