--- /dev/null
+<?xml version='1.0'?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+
+<refentry id="uid0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>uid0</title>
+ <productname>systemd</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>uid0</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>uid0</refname>
+ <refpurpose>Elevate privileges</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>uid0</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="opt" rep="repeat">COMMAND</arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>uid0</command> may be used to temporarily and interactively acquire elavated or different
+ privileges. It serves a similar purpose as <citerefentry
+ project='man-pages'><refentrytitle>sudo</refentrytitle><manvolnum>8</manvolnum></citerefentry>, but
+ operates differently in a couple of key areas:</para>
+
+ <itemizedlist>
+ <listitem><para>No execution or security context credentials are inherited from the caller into the
+ invoked commands, as they are invoked from a fresh, isolated service forked off the service
+ manager.</para></listitem>
+
+ <listitem><para>Authentication takes place via <ulink
+ url="https://www.freedesktop.org/wiki/Software/polkit">polkit</ulink>, thus isolating the
+ authentication prompt from the terminal (if possible).</para></listitem>
+
+ <listitem><para>An independent pseudo-tty is allocated for the invoked command, detaching its lifecycle and
+ isolating it for security.</para></listitem>
+
+ <listitem><para>No SetUID/SetGID file access bit functionality is used for the implementation.</para></listitem>
+ </itemizedlist>
+
+ <para>Altogether this should provide a safer and more robust alternative to the <command>sudo</command>
+ mechanism, in particular in OS environments where SetUID/SetGID support is not available (for example by
+ setting the <varname>NoNewPrivileges=</varname> variable in
+ <citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>).</para>
+
+ <para>Any session invoked via <command>uid0</command> will run through the
+ <literal>systemd-uid0</literal> PAM stack.</para>
+
+ <para>Note that <command>uid0</command> is implemented as an alternative multi-call invocation of
+ <citerefentry><refentrytitle>systemd-run</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>The following options are understood:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--no-ask-password</option></term>
+
+ <listitem><para>Do not query the user for authentication for privileged operations.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--unit=</option></term>
+
+ <listitem><para>Use this unit name instead of an automatically generated one.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--property=</option></term>
+
+ <listitem><para>Sets a property on the service unit that is created. This option takes an assignment
+ in the same format as
+ <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>'s
+ <command>set-property</command> command.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--description=</option></term>
+
+ <listitem><para>Provide a description for the service unit that is invoked. If not specified,
+ the command itself will be used as a description. See <varname>Description=</varname> in
+ <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+ </para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--slice=</option></term>
+
+ <listitem><para>Make the new <filename>.service</filename> unit part of the specified slice, instead
+ of <filename>user.slice</filename>.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--slice-inherit</option></term>
+
+ <listitem><para>Make the new <filename>.service</filename> unit part of the slice the
+ <command>uid0</command> itself has been invoked in. This option may be combined with
+ <option>--slice=</option>, in which case the slice specified via <option>--slice=</option> is placed
+ within the slice the <command>uid0</command> command is invoked in.</para>
+
+ <para>Example: consider <command>uid0</command> being invoked in the slice
+ <filename>foo.slice</filename>, and the <option>--slice=</option> argument is
+ <filename>bar</filename>. The unit will then be placed under
+ <filename>foo-bar.slice</filename>.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--user=</option></term>
+ <term><option>-u</option></term>
+ <term><option>--group=</option></term>
+ <term><option>-g</option></term>
+
+ <listitem><para>Switches to the specified user/group instead of root.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--nice=</option></term>
+
+ <listitem><para>Runs the invoked session with the specified nice level.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--chdir=</option></term>
+ <term><option>-D</option></term>
+
+ <listitem><para>Runs the invoked session with the specified working directory. If not specified
+ defaults to the client's current working directory if switching to the root user, or the target
+ user's home directory otherwise.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--setenv=<replaceable>NAME</replaceable>[=<replaceable>VALUE</replaceable>]</option></term>
+
+ <listitem><para>Runs the invoked session with the specified environment variable set. This parameter
+ may be used more than once to set multiple variables. When <literal>=</literal> and
+ <replaceable>VALUE</replaceable> are omitted, the value of the variable with the same name in the
+ invoking environment will be used.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
+ <xi:include href="user-system-options.xml" xpointer="machine" />
+ <xi:include href="standard-options.xml" xpointer="help" />
+ <xi:include href="standard-options.xml" xpointer="version" />
+ </variablelist>
+
+ <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>
+ </refsect1>
+
+ <refsect1>
+ <title>Exit status</title>
+
+ <para>On success, 0 is returned. If <command>uid0</command> failed to start the session or the specified command fails, a
+ non-zero return value will be returned.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-run</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry project='man-pages'><refentrytitle>sudo</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>machinectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+
+</refentry>
static char *arg_working_directory = NULL;
static bool arg_shell = false;
static char **arg_cmdline = NULL;
+static char *arg_exec_path = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_description, freep);
STATIC_DESTRUCTOR_REGISTER(arg_environment, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_timer_property, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_working_directory, freep);
STATIC_DESTRUCTOR_REGISTER(arg_cmdline, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_exec_path, freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
return 0;
}
+static int help_sudo_mode(void) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("uid0", "1", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%s [OPTIONS...] COMMAND [ARGUMENTS...]\n"
+ "\n%sElevate privileges interactively.%s\n\n"
+ " -h --help Show this help\n"
+ " -V --version Show package version\n"
+ " --no-ask-password Do not prompt for password\n"
+ " --machine=CONTAINER Operate on local container\n"
+ " --unit=UNIT Run under the specified unit name\n"
+ " --property=NAME=VALUE Set service or scope unit property\n"
+ " --description=TEXT Description for unit\n"
+ " --slice=SLICE Run in the specified slice\n"
+ " --slice-inherit Inherit the slice\n"
+ " -u --user=USER Run as system user\n"
+ " -g --group=GROUP Run as system group\n"
+ " --nice=NICE Nice level\n"
+ " -D --chdir=PATH Set working directory\n"
+ " --setenv=NAME[=VALUE] Set environment variable\n"
+ "\nSee the %s for details.\n",
+ program_invocation_short_name,
+ ansi_highlight(),
+ ansi_normal(),
+ link);
+
+ return 0;
+}
+
static int add_timer_property(const char *name, const char *val) {
char *p;
return 0;
}
+static char **make_login_shell_cmdline(const char *shell) {
+ _cleanup_free_ char *argv0 = NULL;
+
+ assert(shell);
+
+ argv0 = strjoin("-", shell); /* The - is how shells determine if they shall be consider login shells */
+ if (!argv0)
+ return NULL;
+
+ return strv_new(argv0);
+}
+
static int parse_argv(int argc, char *argv[]) {
enum {
return 1;
}
+static int parse_argv_sudo_mode(int argc, char *argv[]) {
+
+ enum {
+ ARG_NO_ASK_PASSWORD = 0x100,
+ ARG_HOST,
+ ARG_MACHINE,
+ ARG_UNIT,
+ ARG_PROPERTY,
+ ARG_DESCRIPTION,
+ ARG_SLICE,
+ ARG_SLICE_INHERIT,
+ ARG_NICE,
+ ARG_SETENV,
+ };
+
+ /* If invoked as "uid0" binary, let's expose a more sudo-like interface. We add various extensions
+ * though (but limit the extension to long options). */
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
+ { "machine", required_argument, NULL, ARG_MACHINE },
+ { "unit", required_argument, NULL, ARG_UNIT },
+ { "property", required_argument, NULL, ARG_PROPERTY },
+ { "description", required_argument, NULL, ARG_DESCRIPTION },
+ { "slice", required_argument, NULL, ARG_SLICE },
+ { "slice-inherit", no_argument, NULL, ARG_SLICE_INHERIT },
+ { "user", required_argument, NULL, 'u' },
+ { "group", required_argument, NULL, 'g' },
+ { "nice", required_argument, NULL, ARG_NICE },
+ { "chdir", required_argument, NULL, 'D' },
+ { "setenv", required_argument, NULL, ARG_SETENV },
+ {},
+ };
+
+ int r, c;
+
+ assert(argc >= 0);
+ assert(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:", options, NULL)) >= 0)
+
+ switch (c) {
+
+ case 'h':
+ return help_sudo_mode();
+
+ case 'V':
+ return version();
+
+ case ARG_NO_ASK_PASSWORD:
+ arg_ask_password = false;
+ break;
+
+ case ARG_MACHINE:
+ arg_transport = BUS_TRANSPORT_MACHINE;
+ arg_host = optarg;
+ break;
+
+ case ARG_UNIT:
+ arg_unit = optarg;
+ break;
+
+ case ARG_PROPERTY:
+ if (strv_extend(&arg_property, optarg) < 0)
+ return log_oom();
+
+ break;
+
+ case ARG_DESCRIPTION:
+ r = free_and_strdup_warn(&arg_description, optarg);
+ if (r < 0)
+ return r;
+ break;
+
+ case ARG_SLICE:
+ arg_slice = optarg;
+ break;
+
+ case ARG_SLICE_INHERIT:
+ arg_slice_inherit = true;
+ break;
+
+ case 'u':
+ arg_exec_user = optarg;
+ break;
+
+ case 'g':
+ arg_exec_group = optarg;
+ break;
+
+ case ARG_NICE:
+ r = parse_nice(optarg, &arg_nice);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse nice value: %s", optarg);
+
+ arg_nice_set = true;
+ break;
+
+ case 'D':
+ r = parse_path_argument(optarg, true, &arg_working_directory);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case ARG_SETENV:
+ r = strv_env_replace_strdup_passthrough(&arg_environment, optarg);
+ if (r < 0)
+ return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg);
+
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+
+ if (!arg_working_directory) {
+ if (arg_exec_user) {
+ /* When switching to a specific user, also switch to its home directory. */
+ arg_working_directory = strdup("~");
+ if (!arg_working_directory)
+ return log_oom();
+ } else {
+ /* When switching to root without this being specified, then stay in the current directory */
+ r = safe_getcwd(&arg_working_directory);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get current working directory: %m");
+ }
+ }
+
+ arg_service_type = "exec";
+ arg_quiet = true;
+ arg_wait = true;
+ arg_aggressive_gc = true;
+
+ arg_stdio = isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) && isatty(STDERR_FILENO) ? ARG_STDIO_PTY : ARG_STDIO_DIRECT;
+ arg_expand_environment = false;
+ arg_send_sighup = true;
+
+ _cleanup_strv_free_ char **l = NULL;
+ if (argc > optind)
+ l = strv_copy(argv + optind);
+ else {
+ const char *e;
+
+ e = strv_env_get(arg_environment, "SHELL");
+ if (e)
+ arg_exec_path = strdup(e);
+ else {
+ if (arg_transport == BUS_TRANSPORT_LOCAL) {
+ r = get_shell(&arg_exec_path);
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine shell: %m");
+ } else
+ arg_exec_path = strdup("/bin/sh");
+ }
+ if (!arg_exec_path)
+ return log_oom();
+
+ l = make_login_shell_cmdline(arg_exec_path);
+ }
+ if (!l)
+ return log_oom();
+
+ strv_free_and_replace(arg_cmdline, l);
+
+ if (!arg_slice) {
+ arg_slice = strdup("user.slice");
+ if (!arg_slice)
+ return log_oom();
+ }
+
+ _cleanup_free_ char *un = NULL;
+ un = getusername_malloc();
+ if (!un)
+ return log_oom();
+
+ /* Set a bunch of environment variables in a roughly sudo-compatible way */
+ r = strv_env_assign(&arg_environment, "SUDO_USER", un);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set $SUDO_USER environment variable: %m");
+
+ r = strv_env_assignf(&arg_environment, "SUDO_UID", UID_FMT, getuid());
+ if (r < 0)
+ return log_error_errno(r, "Failed to set $SUDO_UID environment variable: %m");
+
+ r = strv_env_assignf(&arg_environment, "SUDO_GID", GID_FMT, getgid());
+ if (r < 0)
+ return log_error_errno(r, "Failed to set $SUDO_GID environment variable: %m");
+
+ if (strv_extendf(&arg_property, "LogExtraFields=ELEVATED_UID=" UID_FMT, getuid()) < 0)
+ return log_oom();
+
+ if (strv_extendf(&arg_property, "LogExtraFields=ELEVATED_GID=" GID_FMT, getgid()) < 0)
+ return log_oom();
+
+ if (strv_extendf(&arg_property, "LogExtraFields=ELEVATED_USER=%s", un) < 0)
+ return log_oom();
+
+ if (strv_extend(&arg_property, "PAMName=systemd-uid0") < 0)
+ return log_oom();
+
+ return 1;
+}
+
static int transient_unit_set_properties(sd_bus_message *m, UnitType t, char **properties) {
int r;
if (r < 0)
return bus_log_create_error(r);
- r = sd_bus_message_append(m, "s", arg_cmdline[0]);
+ r = sd_bus_message_append(m, "s", arg_exec_path ?: arg_cmdline[0]);
if (r < 0)
return bus_log_create_error(r);
}
static bool shall_make_executable_absolute(void) {
+ if (arg_exec_path)
+ return false;
if (strv_isempty(arg_cmdline))
return false;
if (arg_transport != BUS_TRANSPORT_LOCAL)
log_parse_environment();
log_open();
- r = parse_argv(argc, argv);
+ if (invoked_as(argv, "uid0"))
+ r = parse_argv_sudo_mode(argc, argv);
+ else
+ r = parse_argv(argc, argv);
if (r <= 0)
return r;