core: accept "|" ExecStart= prefix to spawn target user's shell

When switching to another user it's oftentimes desirable to also spawn
the target user's shell. sudo supports this via -i flag, run0 currently
doesn't. We don't want to proactively query NSS ourselves, since
that would fall short when operating remotely. Let's instead teach
the service manager to spawn the command using the user's default shell.

I opted for "|" instead of "." in the end because the latter seems
a bit obscure. But happy to change it to something else if a better option
comes up.
This commit is contained in:
Mike Yuan
2025-05-07 18:32:19 +02:00
parent 398b3e4472
commit 5b8bcbcf00
8 changed files with 192 additions and 87 deletions
+50 -25
View File
@@ -1472,11 +1472,12 @@ int bus_property_get_exec_ex_command_list(
return sd_bus_message_close_container(reply);
}
static char *exec_command_flags_to_exec_chars(ExecCommandFlags flags) {
static char* exec_command_flags_to_exec_chars(ExecCommandFlags flags) {
return strjoin(FLAGS_SET(flags, EXEC_COMMAND_IGNORE_FAILURE) ? "-" : "",
FLAGS_SET(flags, EXEC_COMMAND_NO_ENV_EXPAND) ? ":" : "",
FLAGS_SET(flags, EXEC_COMMAND_FULLY_PRIVILEGED) ? "+" : "",
FLAGS_SET(flags, EXEC_COMMAND_NO_SETUID) ? "!" : "");
FLAGS_SET(flags, EXEC_COMMAND_NO_SETUID) ? "!" : "",
FLAGS_SET(flags, EXEC_COMMAND_VIA_SHELL) ? "|" : "");
}
int bus_set_transient_exec_command(
@@ -1504,30 +1505,58 @@ int bus_set_transient_exec_command(
return r;
while ((r = sd_bus_message_enter_container(message, 'r', ex_prop ? "sasas" : "sasb")) > 0) {
_cleanup_strv_free_ char **argv = NULL, **ex_opts = NULL;
_cleanup_strv_free_ char **argv = NULL;
const char *path;
int b;
ExecCommandFlags command_flags;
r = sd_bus_message_read(message, "s", &path);
if (r < 0)
return r;
if (!filename_or_absolute_path_is_valid(path))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"\"%s\" is neither a valid executable name nor an absolute path",
path);
r = sd_bus_message_read_strv(message, &argv);
if (r < 0)
return r;
if (strv_isempty(argv))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"\"%s\" argv cannot be empty", name);
if (ex_prop) {
_cleanup_strv_free_ char **ex_opts = NULL;
r = ex_prop ? sd_bus_message_read_strv(message, &ex_opts) : sd_bus_message_read(message, "b", &b);
if (r < 0)
return r;
r = sd_bus_message_read_strv(message, &ex_opts);
if (r < 0)
return r;
r = exec_command_flags_from_strv(ex_opts, &command_flags);
if (r < 0)
return r;
} else {
int b;
r = sd_bus_message_read(message, "b", &b);
if (r < 0)
return r;
command_flags = b ? EXEC_COMMAND_IGNORE_FAILURE : 0;
}
if (!FLAGS_SET(command_flags, EXEC_COMMAND_VIA_SHELL)) {
if (!filename_or_absolute_path_is_valid(path))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"\"%s\" is neither a valid executable name nor an absolute path",
path);
if (strv_isempty(argv))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"\"%s\" argv cannot be empty", name);
} else {
/* Always normalize path and argv0 to be "sh" */
path = _PATH_BSHELL;
if (strv_isempty(argv))
r = strv_extend(&argv, path);
else
r = free_and_strdup(&argv[0], argv[0][0] == '-' ? "-sh" : "sh");
if (r < 0)
return r;
}
r = sd_bus_message_exit_container(message);
if (r < 0)
@@ -1542,19 +1571,13 @@ int bus_set_transient_exec_command(
*c = (ExecCommand) {
.argv = TAKE_PTR(argv),
.flags = command_flags,
};
r = path_simplify_alloc(path, &c->path);
if (r < 0)
return r;
if (ex_prop) {
r = exec_command_flags_from_strv(ex_opts, &c->flags);
if (r < 0)
return r;
} else if (b)
c->flags |= EXEC_COMMAND_IGNORE_FAILURE;
exec_command_append_list(exec_command, TAKE_PTR(c));
}
@@ -1585,17 +1608,19 @@ int bus_set_transient_exec_command(
_cleanup_free_ char *a = NULL, *exec_chars = NULL;
UnitWriteFlags esc_flags = UNIT_ESCAPE_SPECIFIERS |
(FLAGS_SET(c->flags, EXEC_COMMAND_NO_ENV_EXPAND) ? UNIT_ESCAPE_EXEC_SYNTAX : UNIT_ESCAPE_EXEC_SYNTAX_ENV);
bool via_shell = FLAGS_SET(c->flags, EXEC_COMMAND_VIA_SHELL);
exec_chars = exec_command_flags_to_exec_chars(c->flags);
if (!exec_chars)
return -ENOMEM;
a = unit_concat_strv(c->argv, esc_flags);
a = unit_concat_strv(via_shell ? strv_skip(c->argv, 1) : c->argv, esc_flags);
if (!a)
return -ENOMEM;
if (streq_ptr(c->path, c->argv ? c->argv[0] : NULL))
fprintf(f, "%s=%s%s\n", written_name, exec_chars, a);
if (via_shell || streq(c->path, c->argv[0]))
fprintf(f, "%s=%s%s%s\n",
written_name, exec_chars, via_shell && c->argv[0][0] == '-' ? "@" : "", a);
else {
_cleanup_free_ char *t = NULL;
const char *p;