Compare commits
7 Commits
7c78827c19
...
52e989642d
Author | SHA1 | Date |
---|---|---|
![]() |
52e989642d | |
![]() |
3b65f00f40 | |
![]() |
f813b783e3 | |
![]() |
ed8efaa00f | |
![]() |
f8f8ded640 | |
![]() |
11258d04bd | |
![]() |
458d33ec13 |
4
TODO
4
TODO
|
@ -735,10 +735,6 @@ Features:
|
|||
* 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
|
||||
|
||||
|
|
21
man/run0.xml
21
man/run0.xml
|
@ -167,6 +167,18 @@
|
|||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--login-environment</option></term>
|
||||
<term><option>-i</option></term>
|
||||
|
||||
<listitem><para>Invokes the target user's login shell and runs the specified command (if any) via it.
|
||||
If <option>-D/--chdir=</option> is not specified, additionally switch to the target user's home directory
|
||||
even for the root user.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v258"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--setenv=<replaceable>NAME</replaceable>[=<replaceable>VALUE</replaceable>]</option></term>
|
||||
|
||||
|
@ -290,9 +302,12 @@
|
|||
|
||||
<para>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 <option>--setenv=SHELL=…</option> and currently defaults to the
|
||||
<emphasis>originating user's</emphasis> shell (i.e. not the target user's!) if operating locally, or
|
||||
<filename>/bin/sh</filename> when operating with <option>--machine=</option>.</para>
|
||||
invoke may be controlled via <option>-i/--login-environment</option> - when specified the target user's shell
|
||||
is used - or <option>--setenv=SHELL=…</option>. By default, the <emphasis>originating user's</emphasis> shell
|
||||
is executed if operating locally, or <filename>/bin/sh</filename> when operating with <option>--machine=</option>.</para>
|
||||
|
||||
<para>Note that unlike <command>sudo</command>, <command>run0</command> always spawns shells with login shell
|
||||
semantics, regardless of <option>-i</option>.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
|
|
|
@ -1397,7 +1397,7 @@
|
|||
<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>
|
||||
|
@ -1420,14 +1420,19 @@
|
|||
|
||||
<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 user's default shell. 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
|
||||
<literal>+</literal>/<literal>!</literal>/<literal>!!</literal> may be used together and they can appear in any
|
||||
order. However, only one of <literal>+</literal>, <literal>!</literal>, <literal>!!</literal> may be used at a
|
||||
time.</para>
|
||||
<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>
|
||||
|
||||
<para>For each command, the first argument must be either an absolute path to an executable or a simple
|
||||
file name without any slashes. If the command is not a full (absolute) path, it will be resolved to a
|
||||
|
@ -1490,11 +1495,6 @@ ExecStart=/bin/echo $ONE $TWO $THREE</programlisting>
|
|||
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>
|
||||
<programlisting>ExecStart=sh -c 'dmesg | tac'</programlisting>
|
||||
|
||||
<para>Example:</para>
|
||||
|
||||
<programlisting>ExecStart=echo one
|
||||
|
|
|
@ -884,9 +884,12 @@ int replace_env_argv(
|
|||
char ***ret_bad_variables) {
|
||||
|
||||
_cleanup_strv_free_ char **n = NULL, **unset_variables = NULL, **bad_variables = NULL;
|
||||
size_t k = 0, l = 0;
|
||||
size_t k = 0, l;
|
||||
int r;
|
||||
|
||||
assert(!strv_isempty(argv));
|
||||
assert(ret);
|
||||
|
||||
l = strv_length(argv);
|
||||
|
||||
n = new(char*, l+1);
|
||||
|
|
|
@ -1470,11 +1470,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_PREFIX_SHELL) ? "|" : "");
|
||||
}
|
||||
|
||||
int bus_set_transient_exec_command(
|
||||
|
@ -1502,30 +1503,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 (!path_is_absolute(path) && !filename_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_PREFIX_SHELL)) {
|
||||
if (!path_is_absolute(path) && !filename_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 = "/bin/sh";
|
||||
|
||||
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)
|
||||
|
@ -1540,19 +1569,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));
|
||||
}
|
||||
|
||||
|
@ -1583,17 +1606,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 prefix_shell = FLAGS_SET(c->flags, EXEC_COMMAND_PREFIX_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(prefix_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 (prefix_shell || streq(c->path, c->argv[0]))
|
||||
fprintf(f, "%s=%s%s%s\n",
|
||||
written_name, exec_chars, prefix_shell && c->argv[0][0] == '-' ? "@" : "", a);
|
||||
else {
|
||||
_cleanup_free_ char *t = NULL;
|
||||
const char *p;
|
||||
|
|
|
@ -4649,12 +4649,11 @@ int exec_invoke(
|
|||
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 */
|
||||
|
@ -4896,7 +4895,7 @@ int exec_invoke(
|
|||
|
||||
if (context->user)
|
||||
u = context->user;
|
||||
else if (context->pam_name) {
|
||||
else if (context->pam_name || FLAGS_SET(command->flags, EXEC_COMMAND_PREFIX_SHELL)) {
|
||||
/* If PAM is enabled but no user name is explicitly selected, then use our own one. */
|
||||
own_user = getusername_malloc();
|
||||
if (!own_user) {
|
||||
|
@ -5501,17 +5500,28 @@ int exec_invoke(
|
|||
/* 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_PREFIX_SHELL)) {
|
||||
if (!shell) {
|
||||
log_exec_debug(context, params,
|
||||
"Shell prefixing requested for user without default shell, using /bin/sh: %s",
|
||||
strna(username));
|
||||
assert(streq(path, "/bin/sh"));
|
||||
} 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_exec_struct_errno(context, params, 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));
|
||||
"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;
|
||||
|
@ -5935,10 +5945,13 @@ int exec_invoke(
|
|||
strv_free_and_replace(accum_env, ee);
|
||||
}
|
||||
|
||||
if (!FLAGS_SET(command->flags, EXEC_COMMAND_NO_ENV_EXPAND)) {
|
||||
_cleanup_strv_free_ char **replaced_argv = NULL, **prefixed_argv = NULL;
|
||||
char **final_argv = FLAGS_SET(command->flags, EXEC_COMMAND_PREFIX_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_exec_error_errno(context,
|
||||
|
@ -5963,8 +5976,33 @@ int exec_invoke(
|
|||
"Invalid environment variable name evaluates to an empty string: %s",
|
||||
strna(jb));
|
||||
}
|
||||
} else
|
||||
final_argv = command->argv;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(command->flags, EXEC_COMMAND_PREFIX_SHELL)) {
|
||||
r = strv_extendf(&prefixed_argv, "%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(&prefixed_argv, "-c", cmdline_joined);
|
||||
if (r < 0) {
|
||||
*exit_status = EXIT_MEMORY;
|
||||
return log_oom();
|
||||
}
|
||||
}
|
||||
|
||||
final_argv = prefixed_argv;
|
||||
}
|
||||
|
||||
log_command_line(context, params, "Executing", executable, final_argv);
|
||||
|
||||
|
|
|
@ -888,7 +888,7 @@ int config_parse_exec(
|
|||
bool semicolon;
|
||||
|
||||
do {
|
||||
_cleanup_free_ char *path = NULL, *firstword = NULL;
|
||||
_cleanup_free_ char *firstword = NULL;
|
||||
|
||||
semicolon = false;
|
||||
|
||||
|
@ -915,6 +915,8 @@ int config_parse_exec(
|
|||
*
|
||||
* "-": 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
|
||||
|
@ -926,6 +928,8 @@ int config_parse_exec(
|
|||
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_PREFIX_SHELL))
|
||||
flags |= EXEC_COMMAND_PREFIX_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)
|
||||
|
@ -947,46 +951,60 @@ int config_parse_exec(
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
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 (!(path_is_absolute(path) ? path_is_valid(path) : filename_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;
|
||||
}
|
||||
|
||||
_cleanup_strv_free_ char **args = NULL;
|
||||
_cleanup_free_ char *path = NULL;
|
||||
|
||||
if (!separate_argv0)
|
||||
if (strv_extend(&args, path) < 0)
|
||||
if (FLAGS_SET(flags, EXEC_COMMAND_PREFIX_SHELL)) {
|
||||
/* Use /bin/sh 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("/bin/sh");
|
||||
if (!path)
|
||||
return log_oom();
|
||||
|
||||
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 (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 (!(path_is_absolute(path) ? path_is_valid(path) : filename_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();
|
||||
}
|
||||
|
||||
while (!isempty(p)) {
|
||||
_cleanup_free_ char *word = NULL, *resolved = NULL;
|
||||
|
||||
|
|
|
@ -3204,7 +3204,7 @@ int service_deserialize_exec_command(
|
|||
bool control, found = false, last = false;
|
||||
int r;
|
||||
|
||||
enum ExecCommandState {
|
||||
enum {
|
||||
STATE_EXEC_COMMAND_TYPE,
|
||||
STATE_EXEC_COMMAND_INDEX,
|
||||
STATE_EXEC_COMMAND_PATH,
|
||||
|
@ -3230,6 +3230,7 @@ int service_deserialize_exec_command(
|
|||
break;
|
||||
|
||||
switch (state) {
|
||||
|
||||
case STATE_EXEC_COMMAND_TYPE:
|
||||
id = service_exec_command_from_string(arg);
|
||||
if (id < 0)
|
||||
|
@ -3237,11 +3238,12 @@ int service_deserialize_exec_command(
|
|||
|
||||
state = STATE_EXEC_COMMAND_INDEX;
|
||||
break;
|
||||
|
||||
case STATE_EXEC_COMMAND_INDEX:
|
||||
/* PID 1234 is serialized as either '1234' or '+1234'. The second form is used to
|
||||
* mark the last command in a sequence. We warn if the deserialized command doesn't
|
||||
* match what we have loaded from the unit, but we don't need to warn if that is the
|
||||
* last command. */
|
||||
/* ExecCommand index 1234 is serialized as either '1234' or '+1234'. The second form
|
||||
* is used to mark the last command in a sequence. We warn if the deserialized command
|
||||
* doesn't match what we have loaded from the unit, but we don't need to warn if
|
||||
* that is the last command. */
|
||||
|
||||
r = safe_atou(arg, &idx);
|
||||
if (r < 0)
|
||||
|
@ -3250,15 +3252,18 @@ int service_deserialize_exec_command(
|
|||
|
||||
state = STATE_EXEC_COMMAND_PATH;
|
||||
break;
|
||||
|
||||
case STATE_EXEC_COMMAND_PATH:
|
||||
path = TAKE_PTR(arg);
|
||||
state = STATE_EXEC_COMMAND_ARGS;
|
||||
break;
|
||||
|
||||
case STATE_EXEC_COMMAND_ARGS:
|
||||
r = strv_extend(&argv, arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
|
|
@ -101,6 +101,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_login_environment = false;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_description, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_environment, strv_freep);
|
||||
|
@ -212,6 +213,7 @@ 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"
|
||||
" -i --login-environment Invoke command via target user's login shell and home\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"
|
||||
|
@ -254,7 +256,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);
|
||||
|
@ -842,6 +844,7 @@ 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' },
|
||||
{ "login-environment", no_argument, NULL, 'i' },
|
||||
{ "setenv", required_argument, NULL, ARG_SETENV },
|
||||
{ "background", required_argument, NULL, ARG_BACKGROUND },
|
||||
{ "pty", no_argument, NULL, ARG_PTY },
|
||||
|
@ -861,7 +864,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) {
|
||||
|
||||
|
@ -975,6 +978,10 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) {
|
|||
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
arg_login_environment = true;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
|
@ -991,7 +998,7 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) {
|
|||
}
|
||||
|
||||
if (!arg_working_directory) {
|
||||
if (arg_exec_user) {
|
||||
if (arg_exec_user || arg_login_environment) {
|
||||
/* When switching to a specific user, also switch to its home directory. */
|
||||
arg_working_directory = strdup("~");
|
||||
if (!arg_working_directory)
|
||||
|
@ -1023,9 +1030,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_login_environment) {
|
||||
const char *e;
|
||||
|
||||
e = strv_env_get(arg_environment, "SHELL");
|
||||
|
@ -1050,9 +1059,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_login_environment) {
|
||||
arg_exec_path = strdup("/bin/sh");
|
||||
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);
|
||||
|
||||
|
@ -1092,6 +1111,12 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) {
|
|||
if (strv_extend(&arg_property, "PAMName=systemd-run0") < 0)
|
||||
return log_oom();
|
||||
|
||||
/* The service manager ignores SIGPIPE for all spawned processes by default. Let's explicitly override
|
||||
* that here, since we're primarily invoked in interactive environments, and the termination of
|
||||
* local terminal session should be acknowledged by remote even for --pipe stdio. */
|
||||
if (strv_extend(&arg_property, "IgnoreSIGPIPE=no") < 0)
|
||||
return log_oom();
|
||||
|
||||
if (!arg_background && arg_stdio == ARG_STDIO_PTY && shall_tint_background()) {
|
||||
double hue;
|
||||
|
||||
|
@ -1260,10 +1285,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_login_environment;
|
||||
|
||||
assert(m);
|
||||
assert((!!pty_path) == (pty_fd >= 0));
|
||||
|
@ -1450,7 +1473,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_login_environment ? EXEC_COMMAND_PREFIX_SHELL : 0),
|
||||
&opts);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to format execute flags: %m");
|
||||
|
@ -2810,7 +2835,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_login_environment) {
|
||||
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);
|
||||
|
|
|
@ -331,17 +331,28 @@ static int bus_append_exec_command(sd_bus_message *m, const char *field, const c
|
|||
}
|
||||
break;
|
||||
|
||||
case '|':
|
||||
if (FLAGS_SET(flags, EXEC_COMMAND_PREFIX_SHELL))
|
||||
done = true;
|
||||
else {
|
||||
flags |= EXEC_COMMAND_PREFIX_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_PREFIX_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) {
|
||||
|
@ -350,7 +361,12 @@ static int bus_append_exec_command(sd_bus_message *m, const char *field, const c
|
|||
return log_error_errno(r, "Failed to convert ExecCommandFlags to strv: %m");
|
||||
}
|
||||
|
||||
if (explicit_path) {
|
||||
if (FLAGS_SET(flags, EXEC_COMMAND_PREFIX_SHELL)) {
|
||||
path = strdup("/bin/sh");
|
||||
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");
|
||||
|
@ -360,11 +376,17 @@ static int bus_append_exec_command(sd_bus_message *m, const char *field, const c
|
|||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse command line: %m");
|
||||
|
||||
if (FLAGS_SET(flags, EXEC_COMMAND_PREFIX_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);
|
||||
|
||||
|
|
|
@ -488,6 +488,7 @@ static const char* const exec_command_strings[] = {
|
|||
"privileged", /* EXEC_COMMAND_FULLY_PRIVILEGED */
|
||||
"no-setuid", /* EXEC_COMMAND_NO_SETUID */
|
||||
"no-env-expand", /* EXEC_COMMAND_NO_ENV_EXPAND */
|
||||
"prefix-shell", /* EXEC_COMMAND_PREFIX_SHELL */
|
||||
};
|
||||
|
||||
assert_cc((1 << ELEMENTSOF(exec_command_strings)) - 1 == _EXEC_COMMAND_FLAGS_ALL);
|
||||
|
|
|
@ -49,8 +49,9 @@ typedef enum ExecCommandFlags {
|
|||
EXEC_COMMAND_FULLY_PRIVILEGED = 1 << 1,
|
||||
EXEC_COMMAND_NO_SETUID = 1 << 2,
|
||||
EXEC_COMMAND_NO_ENV_EXPAND = 1 << 3,
|
||||
EXEC_COMMAND_PREFIX_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);
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
[Service]
|
||||
Type=oneshot
|
||||
Environment=SHLVL=100
|
||||
ExecStartPre=-|false
|
||||
ExecStart=|@echo with login shell $$SHELL: lvl $$SHLVL
|
||||
ExecStart=:|"str='with normal shell'" printenv str
|
||||
ExecStart=|echo YAY! >/tmp/TEST-07-PID1.prefix-shell.flag
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# shellcheck disable=SC2016
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
# shellcheck source=test/units/util.sh
|
||||
. "$(dirname "$0")"/util.sh
|
||||
|
||||
systemd-run --wait -p ExecStartPre="|true" \
|
||||
-p ExecStartPre="|@echo a >/tmp/TEST-07-PID1.prefix-shell.flag" \
|
||||
true
|
||||
assert_eq "$(cat /tmp/TEST-07-PID1.prefix-shell.flag)" "a"
|
||||
rm /tmp/TEST-07-PID1.prefix-shell.flag
|
||||
|
||||
systemctl start prefix-shell.service
|
||||
assert_eq "$(cat /tmp/TEST-07-PID1.prefix-shell.flag)" "YAY!"
|
||||
|
||||
journalctl --sync
|
||||
journalctl -b -u prefix-shell.service --grep "with login shell .*: lvl 101"
|
||||
journalctl -b -u prefix-shell.service --grep "with normal shell"
|
|
@ -256,10 +256,20 @@ if [[ -e /usr/lib/pam.d/systemd-run0 ]] || [[ -e /etc/pam.d/systemd-run0 ]]; the
|
|||
# Validate that we actually went properly through PAM (XDG_SESSION_TYPE is set by pam_systemd)
|
||||
assert_eq "$(run0 ${tu:+"--user=$tu"} bash -c 'echo $XDG_SESSION_TYPE')" "unspecified"
|
||||
|
||||
# Test shell prefixing
|
||||
assert_eq "$(run0 ${tu:+"--user=$tu"} --setenv=SHLVL=10 printenv SHLVL)" "10"
|
||||
if [[ ! -v ASAN_OPTIONS ]]; then
|
||||
assert_eq "$(run0 ${tu:+"--user=$tu"} --setenv=SHLVL=10 -i echo \$SHLVL)" "11"
|
||||
fi
|
||||
|
||||
if [[ -n "$tu" ]]; then
|
||||
# Validate that $SHELL is set to login shell of target user when cmdline is supplied (not invoking shell)
|
||||
TARGET_LOGIN_SHELL="$(getent passwd "$tu" | cut -d: -f7)"
|
||||
assert_eq "$(run0 --user="$tu" printenv SHELL)" "$TARGET_LOGIN_SHELL"
|
||||
# ... or when the command is chained by login shell
|
||||
if [[ ! -v ASAN_OPTIONS ]]; then
|
||||
assert_eq "$(run0 --user="$tu" -i printenv SHELL)" "$TARGET_LOGIN_SHELL"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
# Let's chain a couple of run0 calls together, for fun
|
||||
|
|
Loading…
Reference in New Issue