* machined: optionally track nspawn unix-export/ runtime for each machined, and
then update systemd-ssh-proxy so that it can connect to that.
-* add a new ExecStart= flag that inserts the configured user's shell as first
- word in the command line. (maybe use character '.'). Usecase: tool such as
- run0 can use that to spawn the target user's default shell.
-
* introduce mntid_t, and make it 64bit, as apparently the kernel switched to
64bit mount ids
<tbody>
<row>
<entry><literal>@</literal></entry>
- <entry>If the executable path is prefixed with <literal>@</literal>, the second specified token will be passed as <constant>argv[0]</constant> to the executed process (instead of the actual filename), followed by the further arguments specified.</entry>
+ <entry>If the executable path is prefixed with <literal>@</literal>, the second specified token will be passed as <constant>argv[0]</constant> to the executed process (instead of the actual filename), followed by the further arguments specified, unless <literal>|</literal> is also specified, in which case it enables login shell semantics for the shell spawned by prefixing <literal>-</literal> to <constant>argv[0]</constant>.</entry>
</row>
<row>
<entry>Similar to the <literal>+</literal> character discussed above this permits invoking command lines with elevated privileges. However, unlike <literal>+</literal> the <literal>!</literal> character exclusively alters the effect of <varname>User=</varname>, <varname>Group=</varname> and <varname>SupplementaryGroups=</varname>, i.e. only the stanzas that affect user and group credentials. Note that this setting may be combined with <varname>DynamicUser=</varname>, in which case a dynamic user/group pair is allocated before the command is invoked, but credential changing is left to the executed process itself.</entry>
</row>
+
+ <row>
+ <entry><literal>|</literal></entry>
+
+ <entry>If <literal>|</literal> is specified standalone as executable path, invoke the default shell of <varname>User=</varname>. If specified as a prefix, use the shell (<literal>-c</literal>) to spawn the executable. When <literal>@</literal> is used in conjunction, <constant>argv[0]</constant> of shell will be prefixed with <literal>-</literal> to enable login shell semantics.</entry>
+ </row>
</tbody>
</tgroup>
</table>
- <para><literal>@</literal>, <literal>-</literal>, <literal>:</literal>, and one of
+ <para><literal>@</literal>, <literal>|</literal>, <literal>-</literal>, <literal>:</literal>, and one of
<literal>+</literal>/<literal>!</literal> may be used together and they can appear in any order.
However, <literal>+</literal> and <literal>!</literal> may not be specified at the same time.</para>
includes e.g. <varname>$USER</varname>, but not
<varname>$TERM</varname>).</para>
- <para>Note that shell command lines are not directly supported. If
- shell command lines are to be used, they need to be passed
- explicitly to a shell implementation of some kind. Example:</para>
+ <para>Note that shell command lines are not directly supported, and <literal>|</literal> invokes the user's
+ default shell which isn't deterministic. It's recommended to specify a shell implementation explicitly
+ if portability is desired. Example:</para>
<programlisting>ExecStart=sh -c 'dmesg | tac'</programlisting>
<para>Example:</para>
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(
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)
*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));
}
_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;
const CGroupContext *cgroup_context,
int *exit_status) {
- _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **joined_exec_search_path = NULL, **accum_env = NULL, **replaced_argv = NULL;
+ _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **joined_exec_search_path = NULL, **accum_env = NULL;
int r;
const char *username = NULL, *groupname = NULL;
_cleanup_free_ char *home_buffer = NULL, *memory_pressure_path = NULL, *own_user = NULL;
const char *pwent_home = NULL, *shell = NULL;
- char **final_argv = NULL;
dev_t journal_stream_dev = 0;
ino_t journal_stream_ino = 0;
bool needs_sandboxing, /* Do we need to set up full sandboxing? (i.e. all namespacing, all MAC stuff, caps, yadda yadda */
if (context->user)
u = context->user;
- else if (context->pam_name) {
+ else if (context->pam_name || FLAGS_SET(command->flags, EXEC_COMMAND_VIA_SHELL)) {
/* If PAM is enabled but no user name is explicitly selected, then use our own one. */
own_user = getusername_malloc();
if (!own_user) {
/* Now that the mount namespace has been set up and privileges adjusted, let's look for the thing we
* shall execute. */
+ const char *path = command->path;
+
+ if (FLAGS_SET(command->flags, EXEC_COMMAND_VIA_SHELL)) {
+ if (shell_is_placeholder(shell)) {
+ log_debug("Shell prefixing requested for user without default shell, using /bin/sh: %s",
+ strna(username));
+ assert(streq(path, _PATH_BSHELL));
+ } else
+ path = shell;
+ }
+
_cleanup_free_ char *executable = NULL;
_cleanup_close_ int executable_fd = -EBADF;
- r = find_executable_full(command->path, /* root= */ NULL, context->exec_search_path, false, &executable, &executable_fd);
+ r = find_executable_full(path, /* root= */ NULL, context->exec_search_path, false, &executable, &executable_fd);
if (r < 0) {
*exit_status = EXIT_EXEC;
log_struct_errno(LOG_NOTICE, r,
LOG_MESSAGE_ID(SD_MESSAGE_SPAWN_FAILED_STR),
- LOG_EXEC_MESSAGE(params,
- "Unable to locate executable '%s': %m",
- command->path),
- LOG_ITEM("EXECUTABLE=%s", command->path));
+ LOG_EXEC_MESSAGE(params, "Unable to locate executable '%s': %m", path),
+ LOG_ITEM("EXECUTABLE=%s", path));
/* If the error will be ignored by manager, tune down the log level here. Missing executable
* is very much expected in this case. */
return r != -ENOMEM && FLAGS_SET(command->flags, EXEC_COMMAND_IGNORE_FAILURE) ? 1 : r;
strv_free_and_replace(accum_env, ee);
}
- if (!FLAGS_SET(command->flags, EXEC_COMMAND_NO_ENV_EXPAND)) {
+ _cleanup_strv_free_ char **replaced_argv = NULL, **argv_via_shell = NULL;
+ char **final_argv = FLAGS_SET(command->flags, EXEC_COMMAND_VIA_SHELL) ? strv_skip(command->argv, 1) : command->argv;
+
+ if (final_argv && !FLAGS_SET(command->flags, EXEC_COMMAND_NO_ENV_EXPAND)) {
_cleanup_strv_free_ char **unset_variables = NULL, **bad_variables = NULL;
- r = replace_env_argv(command->argv, accum_env, &replaced_argv, &unset_variables, &bad_variables);
+ r = replace_env_argv(final_argv, accum_env, &replaced_argv, &unset_variables, &bad_variables);
if (r < 0) {
*exit_status = EXIT_MEMORY;
return log_error_errno(r, "Failed to replace environment variables: %m");
_cleanup_free_ char *jb = strv_join(bad_variables, ", ");
log_warning("Invalid environment variable name evaluates to an empty string: %s", strna(jb));
}
- } else
- final_argv = command->argv;
+ }
+
+ if (FLAGS_SET(command->flags, EXEC_COMMAND_VIA_SHELL)) {
+ r = strv_extendf(&argv_via_shell, "%s%s", command->argv[0][0] == '-' ? "-" : "", path);
+ if (r < 0) {
+ *exit_status = EXIT_MEMORY;
+ return log_oom();
+ }
+
+ if (!strv_isempty(final_argv)) {
+ _cleanup_free_ char *cmdline_joined = NULL;
+
+ cmdline_joined = strv_join(final_argv, " ");
+ if (!cmdline_joined) {
+ *exit_status = EXIT_MEMORY;
+ return log_oom();
+ }
+
+ r = strv_extend_many(&argv_via_shell, "-c", cmdline_joined);
+ if (r < 0) {
+ *exit_status = EXIT_MEMORY;
+ return log_oom();
+ }
+ }
+
+ final_argv = argv_via_shell;
+ }
log_command_line(context, params, "Executing", executable, final_argv);
bool semicolon;
do {
- _cleanup_free_ char *path = NULL, *firstword = NULL;
+ _cleanup_free_ char *firstword = NULL;
semicolon = false;
*
* "-": Ignore if the path doesn't exist
* "@": Allow overriding argv[0] (supplied as a separate argument)
+ * "|": Prefix the cmdline with target user's shell (when combined with "@" invoke
+ * login shell semantics)
* ":": Disable environment variable substitution
* "+": Run with full privileges and no sandboxing
* "!": Apply sandboxing except for user/group credentials
separate_argv0 = true;
else if (*f == ':' && !FLAGS_SET(flags, EXEC_COMMAND_NO_ENV_EXPAND))
flags |= EXEC_COMMAND_NO_ENV_EXPAND;
+ else if (*f == '|' && !FLAGS_SET(flags, EXEC_COMMAND_VIA_SHELL))
+ flags |= EXEC_COMMAND_VIA_SHELL;
else if (*f == '+' && !(flags & (EXEC_COMMAND_FULLY_PRIVILEGED|EXEC_COMMAND_NO_SETUID)) && !ambient_hack)
flags |= EXEC_COMMAND_FULLY_PRIVILEGED;
else if (*f == '!' && !(flags & (EXEC_COMMAND_FULLY_PRIVILEGED|EXEC_COMMAND_NO_SETUID)) && !ambient_hack)
ignore = FLAGS_SET(flags, EXEC_COMMAND_IGNORE_FAILURE);
- r = unit_path_printf(u, f, &path);
- if (r < 0) {
- log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, r,
- "Failed to resolve unit specifiers in '%s'%s: %m",
- f, ignore ? ", ignoring" : "");
- return ignore ? 0 : -ENOEXEC;
- }
+ _cleanup_strv_free_ char **args = NULL;
+ _cleanup_free_ char *path = NULL;
+
+ if (FLAGS_SET(flags, EXEC_COMMAND_VIA_SHELL)) {
+ /* Use _PATH_BSHELL as placeholder since we can't do NSS lookups in pid1. This would
+ * be exported to various dbus properties and is used to determine SELinux label -
+ * which isn't accurate, but is a best-effort thing to assume all shells have more
+ * or less the same label. */
+ path = strdup(_PATH_BSHELL);
+ if (!path)
+ return log_oom();
- if (isempty(path)) {
- log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, 0,
- "Empty path in command line%s: %s",
- ignore ? ", ignoring" : "", rvalue);
- return ignore ? 0 : -ENOEXEC;
- }
- if (!string_is_safe(path)) {
- log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, 0,
- "Executable path contains special characters%s: %s",
- ignore ? ", ignoring" : "", path);
- return ignore ? 0 : -ENOEXEC;
- }
- if (path_implies_directory(path)) {
- log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, 0,
- "Executable path specifies a directory%s: %s",
- ignore ? ", ignoring" : "", path);
- return ignore ? 0 : -ENOEXEC;
- }
+ if (strv_extend_many(&args, separate_argv0 ? "-sh" : "sh", empty_to_null(f)) < 0)
+ return log_oom();
+ } else {
+ r = unit_path_printf(u, f, &path);
+ if (r < 0) {
+ log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, r,
+ "Failed to resolve unit specifiers in '%s'%s: %m",
+ f, ignore ? ", ignoring" : "");
+ return ignore ? 0 : -ENOEXEC;
+ }
- if (!filename_or_absolute_path_is_valid(path)) {
- log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, 0,
- "Neither a valid executable name nor an absolute path%s: %s",
- ignore ? ", ignoring" : "", path);
- return ignore ? 0 : -ENOEXEC;
- }
+ if (isempty(path)) {
+ log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, 0,
+ "Empty path in command line%s: %s",
+ ignore ? ", ignoring" : "", rvalue);
+ return ignore ? 0 : -ENOEXEC;
+ }
+ if (!string_is_safe(path)) {
+ log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, 0,
+ "Executable path contains special characters%s: %s",
+ ignore ? ", ignoring" : "", path);
+ return ignore ? 0 : -ENOEXEC;
+ }
+ if (path_implies_directory(path)) {
+ log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, 0,
+ "Executable path specifies a directory%s: %s",
+ ignore ? ", ignoring" : "", path);
+ return ignore ? 0 : -ENOEXEC;
+ }
- _cleanup_strv_free_ char **args = NULL;
+ if (!filename_or_absolute_path_is_valid(path)) {
+ log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, 0,
+ "Neither a valid executable name nor an absolute path%s: %s",
+ ignore ? ", ignoring" : "", path);
+ return ignore ? 0 : -ENOEXEC;
+ }
- if (!separate_argv0)
- if (strv_extend(&args, path) < 0)
- return log_oom();
+ if (!separate_argv0)
+ if (strv_extend(&args, path) < 0)
+ return log_oom();
+ }
while (!isempty(p)) {
_cleanup_free_ char *word = NULL, *resolved = NULL;
}
break;
+ case '|':
+ if (FLAGS_SET(flags, EXEC_COMMAND_VIA_SHELL))
+ done = true;
+ else {
+ flags |= EXEC_COMMAND_VIA_SHELL;
+ eq++;
+ }
+ break;
+
default:
done = true;
}
} while (!done);
- if (!is_ex_prop && (flags & (EXEC_COMMAND_NO_ENV_EXPAND|EXEC_COMMAND_FULLY_PRIVILEGED|EXEC_COMMAND_NO_SETUID))) {
+ if (!is_ex_prop && (flags & (EXEC_COMMAND_NO_ENV_EXPAND|EXEC_COMMAND_FULLY_PRIVILEGED|EXEC_COMMAND_NO_SETUID|EXEC_COMMAND_VIA_SHELL))) {
/* Upgrade the ExecXYZ= property to ExecXYZEx= for convenience */
is_ex_prop = true;
+
upgraded_name = strjoin(field, "Ex");
if (!upgraded_name)
return log_oom();
+ field = upgraded_name;
}
if (is_ex_prop) {
return log_error_errno(r, "Failed to convert ExecCommandFlags to strv: %m");
}
- if (explicit_path) {
+ if (FLAGS_SET(flags, EXEC_COMMAND_VIA_SHELL)) {
+ path = strdup(_PATH_BSHELL);
+ if (!path)
+ return log_oom();
+
+ } else if (explicit_path) {
r = extract_first_word(&eq, &path, NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE);
if (r < 0)
return log_error_errno(r, "Failed to parse path: %m");
if (r < 0)
return log_error_errno(r, "Failed to parse command line: %m");
+ if (FLAGS_SET(flags, EXEC_COMMAND_VIA_SHELL)) {
+ r = strv_prepend(&l, explicit_path ? "-sh" : "sh");
+ if (r < 0)
+ return log_oom();
+ }
+
r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
if (r < 0)
return bus_log_create_error(r);
- r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, upgraded_name ?: field);
+ r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field);
if (r < 0)
return bus_log_create_error(r);
"privileged", /* EXEC_COMMAND_FULLY_PRIVILEGED */
"no-setuid", /* EXEC_COMMAND_NO_SETUID */
"no-env-expand", /* EXEC_COMMAND_NO_ENV_EXPAND */
+ "via-shell", /* EXEC_COMMAND_VIA_SHELL */
};
assert_cc((1 << ELEMENTSOF(exec_command_strings)) - 1 == _EXEC_COMMAND_FLAGS_ALL);
EXEC_COMMAND_FULLY_PRIVILEGED = 1 << 1,
EXEC_COMMAND_NO_SETUID = 1 << 2,
EXEC_COMMAND_NO_ENV_EXPAND = 1 << 3,
+ EXEC_COMMAND_VIA_SHELL = 1 << 4,
_EXEC_COMMAND_FLAGS_INVALID = -EINVAL,
- _EXEC_COMMAND_FLAGS_ALL = (1 << 4) -1,
+ _EXEC_COMMAND_FLAGS_ALL = (1 << 5) -1,
} ExecCommandFlags;
int exec_command_flags_from_strv(char * const *ex_opts, ExecCommandFlags *ret);