run0: implement sudo options -k/-K/-v (#42465)
This implements a close facsimile of [sudo](https://man.archlinux.org/man/sudo.8#K)'s -k/-K/-v options, which manipulate the temporary authorizations used by sudo. Use like `run0 -k whoami` to always re-auth, or just `run0 -k` to revoke a prior temporary authorization. ~~Depends on polkit-org/polkit#662 due to a bug in polkit.~~ Now it should work ok despite the bug.
This commit is contained in:
@@ -167,6 +167,37 @@
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>-k</option></term>
|
||||
<term><option>--reset-timestamp</option></term>
|
||||
|
||||
<listitem><para>Revokes temporary polkit authorization for this terminal, if present.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>-K</option></term>
|
||||
<term><option>--remove-timestamp</option></term>
|
||||
|
||||
<listitem><para>Revokes all temporary polkit authorizations for this user session.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>-v</option></term>
|
||||
<term><option>--validate</option></term>
|
||||
|
||||
<listitem><para>Request authorization from polkit. Can be used to create a temporary authorization
|
||||
without running a command.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--via-shell</option></term>
|
||||
|
||||
@@ -326,6 +357,15 @@
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>-n</option></term>
|
||||
<term><option>--non-interactive</option></term>
|
||||
|
||||
<listitem><para>Equivalent to <option>--no-ask-password</option>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="no-ask-password" />
|
||||
<xi:include href="standard-options.xml" xpointer="help" />
|
||||
<xi:include href="standard-options.xml" xpointer="version" />
|
||||
|
||||
@@ -38,6 +38,7 @@ _run0() {
|
||||
--setenv --background
|
||||
)
|
||||
local OPTS="${opts_with_values[*]} -h --help -V --version --no-ask-password --slice-inherit --empower"
|
||||
OPTS="$OPTS -k --reset-timestamp -K --remove-timestamp -v --validate -n --non-interactive"
|
||||
|
||||
local i
|
||||
for (( i=1; i <= COMP_CWORD; i++ )); do
|
||||
|
||||
@@ -37,7 +37,7 @@ _run0_slices() {
|
||||
}
|
||||
|
||||
local -a args=(
|
||||
'--no-ask-password[Do not query the user for authentication]'
|
||||
'(-n --non-interactive --no-ask-password)'{-n,--non-interactive,--no-ask-password}'[Do not query the user for authentication]'
|
||||
'--unit=[Use this unit name instead of an automatically generated one]'
|
||||
{'*--property=','*-p+'}'[Sets a property on the service unit created]:property:_run0_unit_properties'
|
||||
'--description=[Provide a description for the service unit]:TEXT'
|
||||
@@ -47,6 +47,9 @@ local -a args=(
|
||||
'(--group -g)'{--group=,-g+}'[Switch to the specified group]:group:_groups'
|
||||
'--nice=[Run with specified nice level]:nice value'
|
||||
'(--chdir -D -i --same-root-dir)'{--chdir=,-D+}'[Run within the specified working directory]:directory:_files -/'
|
||||
'(-k --reset-timestamp)'{-k,--reset-timestamp}'[Revoke temporary authorization for this terminal]'
|
||||
'(-K --remove-timestamp)'{-K,--remove-timestamp}'[Revoke temporary authorizations for this user session]'
|
||||
'(-v --validate)'{-v,--validate}'[Request authorization from polkit]'
|
||||
'(-i)'--via-shell"[Invoke command via target user's login shell]"
|
||||
'(--via-shell --chdir -D --same-root-dir)'-i"[Shortcut for --via-shell --chdir='~']"
|
||||
'*--setenv=[Set the specified environment variable in the session]:environment variable:_parameters -g "*export*" -S = -q'
|
||||
|
||||
+348
-1
@@ -4,10 +4,12 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/pidfd.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sd-bus-protocol.h"
|
||||
#include "sd-bus.h"
|
||||
#include "sd-daemon.h"
|
||||
#include "sd-event.h"
|
||||
@@ -21,6 +23,7 @@
|
||||
#include "bus-locator.h"
|
||||
#include "bus-map-properties.h"
|
||||
#include "bus-message-util.h"
|
||||
#include "bus-polkit.h"
|
||||
#include "bus-unit-util.h"
|
||||
#include "bus-util.h"
|
||||
#include "bus-wait-for-jobs.h"
|
||||
@@ -72,6 +75,10 @@ static bool arg_scope = false;
|
||||
static bool arg_remain_after_exit = false;
|
||||
static bool arg_no_block = false;
|
||||
static bool arg_wait = false;
|
||||
static bool arg_default_command = false;
|
||||
static bool arg_remove_timestamp = false;
|
||||
static bool arg_reset_timestamp = false;
|
||||
static bool arg_validate = false;
|
||||
static const char *arg_unit = NULL;
|
||||
static char *arg_description = NULL;
|
||||
static char *arg_slice = NULL;
|
||||
@@ -794,6 +801,10 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) {
|
||||
arg_ask_password = false;
|
||||
break;
|
||||
|
||||
OPTION('n', "non-interactive", NULL, "Do not prompt for password"):
|
||||
arg_ask_password = false;
|
||||
break;
|
||||
|
||||
OPTION_LONG("machine", "CONTAINER", "Operate on local container"):
|
||||
r = parse_machine_argument(opts.arg, &arg_host, &arg_transport);
|
||||
if (r < 0)
|
||||
@@ -825,6 +836,18 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) {
|
||||
arg_slice_inherit = true;
|
||||
break;
|
||||
|
||||
OPTION('k', "reset-timestamp", NULL, "Revoke temporary authorization"):
|
||||
arg_reset_timestamp = true;
|
||||
break;
|
||||
|
||||
OPTION('K', "remove-timestamp", NULL, "Revoke all temporary authorizations for this user session"):
|
||||
arg_remove_timestamp = true;
|
||||
break;
|
||||
|
||||
OPTION('v', "validate", NULL, "Request temporary authorization from polkit"):
|
||||
arg_validate = true;
|
||||
break;
|
||||
|
||||
OPTION('u', "user", "USER", "Run as system user"):
|
||||
r = free_and_strdup_warn(&arg_exec_user, opts.arg);
|
||||
if (r < 0)
|
||||
@@ -970,12 +993,16 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) {
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
char **args = option_parser_get_args(&opts);
|
||||
if (!strv_isempty(args)) {
|
||||
if (arg_validate)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Option '--validate' cannot be used with a command");
|
||||
l = strv_copy(args);
|
||||
if (!l)
|
||||
return log_oom();
|
||||
} else if (!arg_via_shell) {
|
||||
const char *e;
|
||||
|
||||
arg_default_command = true;
|
||||
e = strv_env_get(arg_environment, "SHELL");
|
||||
if (e) {
|
||||
arg_exec_path = strdup(e);
|
||||
@@ -2525,7 +2552,7 @@ static int start_transient_scope(sd_bus *bus) {
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_call(bus, m, 0, &error, &reply);
|
||||
r = sd_bus_call(bus, m, /* usec = */ 0, &error, &reply);
|
||||
if (r < 0) {
|
||||
if (sd_bus_error_has_names(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY, SD_BUS_ERROR_PROPERTY_READ_ONLY) && allow_pidfd) {
|
||||
log_debug("Retrying with classic PIDs.");
|
||||
@@ -2855,6 +2882,299 @@ static bool shall_make_executable_absolute(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static int polkit_check_authorization(sd_bus *bus, PolkitFlags flags, char **ret_tmpauthz_id) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
pid_t pid;
|
||||
_cleanup_close_ int pidfd = -EBADF;
|
||||
_cleanup_free_ char *tmpauthz_id = NULL;
|
||||
int is_authorized, is_challenge;
|
||||
int r;
|
||||
|
||||
assert(bus);
|
||||
|
||||
r = sd_bus_message_new_method_call(bus, &m,
|
||||
"org.freedesktop.PolicyKit1",
|
||||
"/org/freedesktop/PolicyKit1/Authority",
|
||||
"org.freedesktop.PolicyKit1.Authority",
|
||||
"CheckAuthorization");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
pid = getpid_cached();
|
||||
|
||||
/* Polkit requires pidfd to honor temporary authorizations */
|
||||
pidfd = pidfd_open(pid, 0);
|
||||
if (pidfd < 0)
|
||||
return log_debug_errno(errno, "pidfd_open failed: %m");
|
||||
|
||||
r = sd_bus_message_append(m, "(sa{sv})s", "unix-process", 4, "pid", "u", (uint32_t) pid,
|
||||
"start-time", "t", UINT64_C(0), "uid", "i", (uint32_t) geteuid(), "pidfd", "h", pidfd,
|
||||
"org.freedesktop.systemd1.manage-units");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append(m, "a{ss}us", /* details = */ 0, (uint32_t) flags, /* cancel_id = */ NULL);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_call(bus, m, /* usec = */ 0, &error, &reply);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to check authorization: %s", bus_error_message(&error, r));
|
||||
|
||||
r = sd_bus_message_enter_container(reply, 'r', "bba{ss}");
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
r = sd_bus_message_read(reply, "bb", &is_authorized, &is_challenge);
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
r = sd_bus_message_enter_container(reply, 'a', "{ss}");
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
for (;;) {
|
||||
const char *key, *value;
|
||||
r = sd_bus_message_enter_container(reply, 'e', "ss");
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
r = sd_bus_message_read(reply, "ss", &key, &value);
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
if (streq(key, "polkit.temporary_authorization_id")) {
|
||||
r = free_and_strdup(&tmpauthz_id, value);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
r = sd_bus_message_exit_container(reply);
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
}
|
||||
|
||||
r = sd_bus_message_exit_container(reply); /* a{ss} */
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
r = sd_bus_message_exit_container(reply); /* (bba{ss}) */
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
if (ret_tmpauthz_id && is_authorized)
|
||||
*ret_tmpauthz_id = TAKE_PTR(tmpauthz_id);
|
||||
|
||||
return is_authorized;
|
||||
}
|
||||
|
||||
static int revoke_temporary_authorization_by_id(sd_bus *bus, const char *id) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
int r;
|
||||
|
||||
assert(bus);
|
||||
assert(id);
|
||||
|
||||
r = sd_bus_message_new_method_call(bus, &m,
|
||||
"org.freedesktop.PolicyKit1",
|
||||
"/org/freedesktop/PolicyKit1/Authority",
|
||||
"org.freedesktop.PolicyKit1.Authority",
|
||||
"RevokeTemporaryAuthorizationById");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append(m, "s", id);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
log_debug("Revoking temporary authorization %s", id);
|
||||
r = sd_bus_call(bus, m, /* usec = */ 0, &error, /* ret_reply= */ NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to revoke temporary authorization %s: %s",
|
||||
id, bus_error_message(&error, r));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_polkit_subject_for_uid(sd_bus_message *m) {
|
||||
const char *kind = NULL;
|
||||
uid_t uid = UID_INVALID;
|
||||
int r;
|
||||
|
||||
r = sd_bus_message_enter_container(m, 'r', "sa{sv}");
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
r = sd_bus_message_read(m, "s", &kind);
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
r = sd_bus_message_enter_container(m, 'a', "{sv}");
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
for (;;) {
|
||||
const char *key, *contents;
|
||||
char type;
|
||||
|
||||
r = sd_bus_message_enter_container(m, 'e', "sv");
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
r = sd_bus_message_read(m, "s", &key);
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
r = sd_bus_message_peek_type(m, &type, &contents);
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
if (streq(key, "pid")) {
|
||||
if (*contents != SD_BUS_TYPE_UINT32)
|
||||
return bus_log_parse_error(SYNTHETIC_ERRNO(EINVAL));
|
||||
r = sd_bus_message_skip(m, "v");
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
} else if (streq(key, "start-time")) {
|
||||
if (*contents != SD_BUS_TYPE_UINT64)
|
||||
return bus_log_parse_error(SYNTHETIC_ERRNO(EINVAL));
|
||||
r = sd_bus_message_skip(m, "v");
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
} else if (streq(key, "uid")) {
|
||||
if (*contents != SD_BUS_TYPE_INT32)
|
||||
return bus_log_parse_error(SYNTHETIC_ERRNO(EINVAL));
|
||||
r = sd_bus_message_read(m, "v", "i", &uid);
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
} else if (streq(key, "pidfd")) {
|
||||
if (*contents != SD_BUS_TYPE_UNIX_FD)
|
||||
return bus_log_parse_error(SYNTHETIC_ERRNO(EINVAL));
|
||||
r = sd_bus_message_skip(m, "v");
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
} else {
|
||||
r = sd_bus_message_skip(m, "v");
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
}
|
||||
|
||||
r = sd_bus_message_exit_container(m);
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
}
|
||||
|
||||
r = sd_bus_message_exit_container(m); /* a(sa{sv}) */
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
r = sd_bus_message_exit_container(m); /* (a(sa{sv})) */
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
return uid_is_valid(uid) && uid == geteuid();
|
||||
}
|
||||
|
||||
static int revoke_temporary_authorizations(sd_bus *bus) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
const char *session_id = NULL;
|
||||
int r;
|
||||
|
||||
assert(bus);
|
||||
|
||||
session_id = getenv("XDG_SESSION_ID");
|
||||
if (!session_id)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "XDG_SESSION_ID is not set");
|
||||
|
||||
r = sd_bus_message_new_method_call(bus, &m,
|
||||
"org.freedesktop.PolicyKit1",
|
||||
"/org/freedesktop/PolicyKit1/Authority",
|
||||
"org.freedesktop.PolicyKit1.Authority",
|
||||
"EnumerateTemporaryAuthorizations");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append(m, "(sa{sv})", "unix-session", 1, "session-id", "s", session_id);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_call(bus, m, /* usec = */ 0, &error, &reply);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enumerate temporary authorizations: %s",
|
||||
bus_error_message(&error, r));
|
||||
|
||||
r = sd_bus_message_enter_container(reply, 'a', "(ss(sa{sv})tt)");
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
for (;;) {
|
||||
const char *id = NULL, *action_id = NULL;
|
||||
|
||||
r = sd_bus_message_enter_container(reply, 'r', "ss(sa{sv})tt");
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
r = sd_bus_message_read(reply, "ss", &id, &action_id);
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
if (streq(action_id, "org.freedesktop.systemd1.manage-units")) {
|
||||
r = check_polkit_subject_for_uid(reply);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0) {
|
||||
r = revoke_temporary_authorization_by_id(bus, id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
} else {
|
||||
r = sd_bus_message_skip(reply, "(sa{sv})");
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
}
|
||||
|
||||
r = sd_bus_message_skip(reply, "tt");
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
r = sd_bus_message_exit_container(reply);
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
}
|
||||
|
||||
r = sd_bus_message_exit_container(reply);
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int polkit_validate(sd_bus *bus) {
|
||||
PolkitFlags flags = POLKIT_ALWAYS_QUERY;
|
||||
int r;
|
||||
|
||||
if (arg_ask_password)
|
||||
flags |= POLKIT_ALLOW_INTERACTIVE;
|
||||
|
||||
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
|
||||
r = polkit_check_authorization(bus, (uint32_t) (flags & _POLKIT_MASK_PUBLIC), NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) /* not authorized */
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run(int argc, char* argv[]) {
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
int r;
|
||||
@@ -2919,6 +3239,33 @@ static int run(int argc, char* argv[]) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (arg_remove_timestamp) {
|
||||
r = revoke_temporary_authorizations(bus);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (arg_validate)
|
||||
return polkit_validate(bus);
|
||||
if (arg_default_command)
|
||||
return 0;
|
||||
} else if (arg_reset_timestamp) {
|
||||
_cleanup_free_ char *tmpauthz_id = NULL;
|
||||
const PolkitFlags flags = POLKIT_ALWAYS_QUERY;
|
||||
r = polkit_check_authorization(bus, (uint32_t) (flags & _POLKIT_MASK_PUBLIC), &tmpauthz_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0 && tmpauthz_id) {
|
||||
r = revoke_temporary_authorization_by_id(bus, tmpauthz_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
if (arg_validate)
|
||||
return polkit_validate(bus);
|
||||
if (arg_default_command)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg_validate)
|
||||
return polkit_validate(bus);
|
||||
if (arg_scope)
|
||||
return start_transient_scope(bus);
|
||||
if (arg_path_property)
|
||||
|
||||
Reference in New Issue
Block a user