Fix to change systemd ACL helper calls

https://github.com/webmin/webmin/pull/2760#discussion_r3449183293
This commit is contained in:
Ilia Ross
2026-06-22 02:14:10 +02:00
parent a1c88df428
commit f2fe6c930f
13 changed files with 158 additions and 141 deletions

View File

@@ -11,7 +11,7 @@ sub backup_config_files
{
my @rv;
my %seen;
return @rv if (!systemd_acl_bool(\%access, 'backup'));
return @rv if (!systemd_acl_bool('backup'));
my $add_file = sub {
my ($file) = @_;
push(@rv, $file) if ($file && !$seen{$file}++);
@@ -29,7 +29,7 @@ foreach my $u (list_units()) {
# User units live under home directories and are safe to include by path.
foreach my $u (list_all_user_units()) {
next if (!systemd_acl_user_allowed(\%access, $u->{'user'}));
next if (!systemd_acl_user_allowed($u->{'user'}));
$add_file->($u->{'file'}) if ($u->{'file'});
my $name = backup_unit_name($u);
if ($name && dropin_exists(1, $u->{'user'}, $name)) {

View File

@@ -11,16 +11,16 @@ our (%access, %config, %text);
ReadParse();
has_command("systemctl") || error($text{'systemd_esystemctl'});
systemd_can_enter_module(\%access) || systemd_acl_error('penter');
systemd_can_enter_module() || systemd_acl_error('penter');
$config{'show_dropin_inventory'} || error($text{'dropins_disabled'});
my $can_system = systemd_can_view_system(\%access);
my $can_user = systemd_can_view_user_scope(\%access);
my $can_system = systemd_can_view_system();
my $can_user = systemd_can_view_user_scope();
my @system_units = $can_system ? list_units() : ( );
my %system_units = map { $_->{'name'}, $_ } @system_units;
my @user_units = $can_user ?
grep { systemd_acl_user_allowed(\%access, $_->{'user'}) }
grep { systemd_acl_user_allowed($_->{'user'}) }
list_all_user_units() : ( );
my %user_units = map { $_->{'user'}."\t".$_->{'name'}, $_ } @user_units;
@@ -28,7 +28,7 @@ my @dropins;
push(@dropins, list_system_dropin_override_files()) if ($can_system);
if ($can_user) {
foreach my $dropin (list_all_user_dropin_override_files()) {
next if (!systemd_acl_user_allowed(\%access, $dropin->{'user'}));
next if (!systemd_acl_user_allowed($dropin->{'user'}));
push(@dropins, $dropin);
}
}
@@ -96,7 +96,7 @@ if ($dropin->{'scope'} eq 'user') {
return ui_tag('i', html_escape($text{'dropins_unit_missing'}))
if (!$user_units->{$user."\t".$unit});
return ui_tag('i', html_escape($text{'dropins_view_only'}))
if (!systemd_can_dropin(\%access, 1, $user));
if (!systemd_can_dropin(1, $user));
my $url = "edit_unit.cgi?scope=user&unituser=".urlize($user).
"&name=".urlize($unit)."&dropin=1".
dropin_file_arg($dropin);
@@ -106,7 +106,7 @@ my $unit = $dropin->{'unit'};
return ui_tag('i', html_escape($text{'dropins_unit_missing'}))
if (!$system_units->{$unit});
return ui_tag('i', html_escape($text{'dropins_view_only'}))
if (!systemd_can_dropin(\%access, 0));
if (!systemd_can_dropin(0));
return ui_link("edit_unit.cgi?name=".urlize($unit)."&dropin=1".
dropin_file_arg($dropin),
$text{'dropins_edit'});

View File

@@ -11,12 +11,12 @@ our (%access, %in, %text);
ReadParse();
error_setup($text{'manual_edit_err'});
systemd_acl_bool(\%access, 'manual') ||
systemd_acl_bool(\%access, 'manual_user') ||
systemd_acl_bool('manual') ||
systemd_acl_bool('manual_user') ||
systemd_acl_error('pmanual');
# File choices are constrained to discovered system and local user unit files.
my @files = grep { systemd_can_manual(\%access, $_) } list_manual_unit_files();
my @files = grep { systemd_can_manual($_) } list_manual_unit_files();
@files || error(manual_empty_message());
my %allowed = map { $_->{'file'}, $_ } @files;
my $info = $allowed{$in{'file'}} || $files[0];
@@ -61,10 +61,10 @@ return html_escape($info->{'file'});
# Returns an empty-state message for the current manual-edit ACL scope.
sub manual_empty_message
{
my $user = systemd_acl_default_user(\%access);
my $user = systemd_acl_default_user();
return text('manual_enone_user',
ui_tag('tt', html_escape($user)))
if ($user && systemd_acl_bool(\%access, 'manual_user') &&
!systemd_acl_bool(\%access, 'manual'));
if ($user && systemd_acl_bool('manual_user') &&
!systemd_acl_bool('manual'));
return $text{'manual_enone'};
}

View File

@@ -127,7 +127,7 @@ my $edit_dropin = !$in{'new'} && $in{'dropin'} ? 1 : 0;
my $dropin_file = $edit_dropin ? clean_unit_value($in{'dropfile'}) : "";
my $dropin_info;
if ($in{'new'} && $create_user_scope && !$unituser) {
$unituser = systemd_acl_default_user(\%access) || "";
$unituser = systemd_acl_default_user() || "";
if (!$unituser) {
my $ruinfo = get_user_details($remote_user);
$unituser = $ruinfo->{'user'}
@@ -149,7 +149,7 @@ my $remote_uinfo = get_user_details($remote_user);
# New units start with an empty record. Existing units are looked up from the
# selected system or user scope so edits cannot cross scopes accidentally.
if ($in{'new'}) {
systemd_can_create(\%access, $create_user_scope,
systemd_can_create($create_user_scope,
$create_user_scope ? $unituser : undef) ||
systemd_acl_error($create_user_scope ?
'pcreate_user' : 'pcreate');
@@ -163,13 +163,13 @@ else {
# The owner must be a real Unix user before we inspect their units.
get_user_details($unituser) ||
error($text{'systemd_euser'});
systemd_can_view_scope(\%access, 1, $unituser) ||
systemd_can_view_scope(1, $unituser) ||
systemd_acl_error('pview_user');
@units = list_user_units($unituser);
}
else {
# System-scope edits use the system unit list.
systemd_can_view_scope(\%access, 0) ||
systemd_can_view_scope(0) ||
systemd_acl_error('pview');
@units = list_units();
}
@@ -187,8 +187,8 @@ else {
}
$unit_file_editable = unit_file_editable($u);
$can_save_unit = $edit_dropin ?
systemd_can_dropin(\%access, $edit_user_scope, $unituser) :
systemd_can_edit(\%access, $edit_user_scope, $unituser);
systemd_can_dropin($edit_user_scope, $unituser) :
systemd_can_edit($edit_user_scope, $unituser);
# Runtime-managed units are inspect-only, so title them as views.
my $title_key = $unit_file_editable && $can_save_unit ?
@@ -479,7 +479,7 @@ if ($in{'new'}) {
1, undef, \@slice_row);
# Startup state is applied after the unit is created.
if (systemd_can_boot(\%access, $create_user_scope,
if (systemd_can_boot($create_user_scope,
$create_user_scope ? $unituser : undef)) {
print ui_table_row(hlink($text{'systemd_boot'}, "systemd_boot"),
ui_yesno_radio("boot", 1));
@@ -490,7 +490,7 @@ if ($in{'new'}) {
my $acl_default_unituser;
if (!$default_unituser) {
$acl_default_unituser =
systemd_acl_default_user(\%access) || "";
systemd_acl_default_user() || "";
$default_unituser = $acl_default_unituser;
if (!$default_unituser) {
$default_unituser = $remote_uinfo->{'user'}
@@ -499,7 +499,7 @@ if ($in{'new'}) {
}
}
my $force_user_scope_create = $create_user_scope &&
!systemd_can_create(\%access, 0) &&
!systemd_can_create(0) &&
(($remote_uinfo && $remote_uinfo->{'uid'} != 0) ||
$acl_default_unituser) ? 1 : 0;
my $force_user_scope_owner = $force_user_scope_create &&
@@ -535,7 +535,7 @@ if ($in{'new'}) {
($create_user_scope ? "" :
" style='display:none'") ]);
}
if (systemd_acl_bool(\%access, 'linger')) {
if (systemd_acl_bool('linger')) {
my $linger_text = $create_user_scope ?
$text{'systemd_linger_user'} : $text{'systemd_linger'};
my $linger_help = $create_user_scope ?
@@ -1010,7 +1010,7 @@ else {
# Only file-backed installable units can have their startup state changed.
if (boot_state_changeable($u->{'unitstate'}, $u->{'name'}) &&
systemd_can_boot(\%access, $edit_user_scope, $unituser)) {
systemd_can_boot($edit_user_scope, $unituser)) {
print ui_table_row(hlink($text{'systemd_boot'}, "systemd_boot"),
ui_yesno_radio("boot", $u->{'boot'}));
}
@@ -1018,7 +1018,7 @@ else {
# User-scope edits allow linger to be managed alongside the raw unit file.
if ($edit_user_scope) {
my $linger_enabled = user_linger_enabled($unituser);
my $linger_field = systemd_can_linger(\%access, $unituser) ?
my $linger_field = systemd_can_linger($unituser) ?
ui_yesno_radio("linger", $linger_enabled) :
html_escape($linger_enabled ? $text{'yes'} : $text{'no'});
print ui_table_row(hlink($text{'systemd_linger_user'},
@@ -1039,13 +1039,11 @@ else {
my @save_buttons = $unit_file_editable && $can_save_unit ?
( [ undef, $text{'save'} ] ) : ( );
my @control_buttons;
my @inspect_buttons = systemd_can_inspect(
\%access, $edit_user_scope, $unituser) ?
my @inspect_buttons = systemd_can_inspect($edit_user_scope, $unituser) ?
( [ 'status', $text{'edit_statusnow'} ],
[ 'props', $text{'edit_propsnow'} ],
[ 'deps', $text{'edit_depsnow'} ] ) : ( );
my @log_buttons = systemd_can_logs(
\%access, $edit_user_scope, $unituser) ?
my @log_buttons = systemd_can_logs($edit_user_scope, $unituser) ?
( [ 'logs', $text{'edit_logsnow'} ] ) : ( );
# Running units can be stopped, but only restart units where systemd
@@ -1054,16 +1052,14 @@ else {
if (defined($u->{'status'}) && $u->{'status'} == 1) {
push(@control_buttons, [ 'restart', $text{'edit_restartnow'} ])
if (unit_restartable($in{'name'}) &&
systemd_can_runtime(
\%access, 'restart', $edit_user_scope,
systemd_can_runtime('restart', $edit_user_scope,
$unituser));
push(@control_buttons, [ 'stop', $text{'edit_stopnow'} ])
if (systemd_can_runtime(
\%access, 'stop', $edit_user_scope,
if (systemd_can_runtime('stop', $edit_user_scope,
$unituser));
}
elsif (unit_startable($in{'name'}) &&
systemd_can_runtime(\%access, 'start',
systemd_can_runtime('start',
$edit_user_scope, $unituser)) {
push(@control_buttons, [ 'start', $text{'edit_startnow'} ]);
}
@@ -1075,7 +1071,7 @@ else {
$text{'edit_stockunitnow'} || "Stock Unit" ]);
}
elsif ($unit_file_editable &&
systemd_can_dropin(\%access, $edit_user_scope, $unituser)) {
systemd_can_dropin($edit_user_scope, $unituser)) {
my $override_text = dropin_exists($edit_user_scope,
$unituser, $in{'name'}) ?
($text{'edit_editoverridenow'} ||
@@ -1086,13 +1082,13 @@ else {
}
my @delete_buttons;
if ($edit_dropin && !$dropin_file && $unit_file_editable &&
systemd_can_dropin(\%access, $edit_user_scope, $unituser)) {
systemd_can_dropin($edit_user_scope, $unituser)) {
push(@delete_buttons,
[ 'delete_override',
$text{'edit_deleteoverridenow'} || "Delete Override" ]);
}
elsif ($unit_file_editable && $in{'name'} ne 'webmin.service' &&
systemd_can_delete(\%access, $edit_user_scope, $unituser)) {
systemd_can_delete($edit_user_scope, $unituser)) {
push(@delete_buttons, [ 'delete', $text{'delete'} ]);
}

View File

@@ -14,7 +14,7 @@ ReadParse();
# The index is GET-addressable, so every value from %in below is either reduced
# to known values or validated before use, and none is printed raw.
has_command("systemctl") || error($text{'systemd_esystemctl'});
systemd_can_enter_module(\%access) || systemd_acl_error('penter');
systemd_can_enter_module() || systemd_acl_error('penter');
# Print the page shell before building the tab contents.
ui_print_header(version_title(),
@@ -25,21 +25,21 @@ print index_style();
# Query parameters only choose the active tab and optional user context.
my $scope = $in{'scope'} eq 'user' && tab_visible('user') ? 'user' : '';
my $unituser = clean_unit_value($in{'unituser'});
$unituser ||= systemd_acl_default_user(\%access) || "";
$unituser ||= systemd_acl_default_user() || "";
my $unituser_details = $unituser ?
get_user_details($unituser) : undef;
if ($scope eq 'user') {
$unituser_details || error($text{'systemd_euser'});
systemd_can_view_user_scope(\%access, $unituser) ||
systemd_can_view_user_scope($unituser) ||
systemd_acl_error('pview_user');
}
$unituser = "" if (!$unituser_details);
# Load both system and user units so the visible tab set matches reality.
my @system_units = systemd_can_view_system(\%access) ? list_units() : ( );
my @system_units = systemd_can_view_system() ? list_units() : ( );
my @user_units = tab_visible('user') &&
systemd_can_view_user_scope(\%access) ?
grep { systemd_acl_user_allowed(\%access, $_->{'user'}) }
systemd_can_view_user_scope() ?
grep { systemd_acl_user_allowed($_->{'user'}) }
list_all_user_units() : ( );
my @tabs = index_tabs(\@system_units, \@user_units, $unituser);
@@ -104,17 +104,17 @@ my @buttons;
push(@buttons, [ "edit_manual.cgi",
$text{'index_edit_files'},
$text{'index_edit_filesdesc'} ])
if (systemd_acl_bool(\%access, 'manual') ||
systemd_acl_bool(\%access, 'manual_user'));
if (systemd_acl_bool('manual') ||
systemd_acl_bool('manual_user'));
push(@buttons, [ "dropins.cgi",
$text{'index_dropins'},
$text{'index_dropinsdesc'} ])
if ($config{'show_dropin_inventory'} &&
systemd_can_enter_module(\%access));
systemd_can_enter_module());
push(@buttons, [ "restart.cgi",
$text{'index_reload'},
$text{'index_reloaddesc'} ])
if (systemd_can_reload(\%access));
if (systemd_can_reload());
return if (!@buttons);
print ui_hr();
print ui_buttons_start();
@@ -185,7 +185,7 @@ foreach my $u (@$system_units) {
my @tabs;
foreach my $group (index_tab_groups()) {
next if (!tab_visible($group->{'id'}));
next if (!systemd_can_view_system(\%access));
next if (!systemd_can_view_system());
my $units = $by_tab{$group->{'id'}} || [ ];
push(@tabs, { %$group,
'title' => index_tab_title($group->{'id'}),
@@ -205,7 +205,7 @@ foreach my $u (@$user_units) {
}
$user_units = \@visible_user_units;
if (tab_visible('user') &&
systemd_can_view_user_scope(\%access, $unituser)) {
systemd_can_view_user_scope($unituser)) {
push(@tabs, { 'id' => 'user',
'user' => 1,
'unituser' => $unituser,
@@ -222,19 +222,19 @@ sub print_index_tab
{
my ($tab, $formno) = @_;
my $user_tab = $tab->{'user'} ? 1 : 0;
my $can_status = systemd_can_inspect(\%access, $user_tab, $tab->{'unituser'});
my $can_logs = systemd_can_logs(\%access, $user_tab, $tab->{'unituser'});
my $can_start = systemd_can_runtime(\%access, 'start',
my $can_status = systemd_can_inspect($user_tab, $tab->{'unituser'});
my $can_logs = systemd_can_logs($user_tab, $tab->{'unituser'});
my $can_start = systemd_can_runtime('start',
$user_tab, $tab->{'unituser'});
my $can_stop = systemd_can_runtime(\%access, 'stop',
my $can_stop = systemd_can_runtime('stop',
$user_tab, $tab->{'unituser'});
my $can_restart = systemd_can_runtime(\%access, 'restart',
my $can_restart = systemd_can_runtime('restart',
$user_tab, $tab->{'unituser'});
my $can_boot = systemd_can_boot(\%access, $user_tab, $tab->{'unituser'});
my $can_boot = systemd_can_boot($user_tab, $tab->{'unituser'});
my $can_mask = $user_tab ? 0 :
systemd_can_mask(\%access, $user_tab, $tab->{'unituser'});
systemd_can_mask($user_tab, $tab->{'unituser'});
my $can_delete = $user_tab ?
systemd_can_delete(\%access, $user_tab, $tab->{'unituser'}) : 0;
systemd_can_delete($user_tab, $tab->{'unituser'}) : 0;
my $selectable = exists($tab->{'selectable'}) ? $tab->{'selectable'} : 1;
$selectable &&= $can_status || $can_logs || $can_start || $can_stop ||
$can_restart || $can_boot || $can_mask || $can_delete ? 1 : 0;
@@ -306,7 +306,7 @@ foreach my $u (@{$tab->{'units'}}) {
$linger_cache{$u->{'user'}} =
user_linger_enabled($u->{'user'});
}
my $linger_html = systemd_can_linger(\%access, $u->{'user'}) ?
my $linger_html = systemd_can_linger($u->{'user'}) ?
linger_toggle_link(
$u->{'user'}, $linger_cache{$u->{'user'}}) :
html_escape($linger_cache{$u->{'user'}} ?
@@ -404,7 +404,7 @@ sub index_create_link
{
my ($tab, $user_tab, $create_type) = @_;
return "" if (!$create_type);
return "" if (!systemd_can_create(\%access, $user_tab, $tab->{'unituser'}));
return "" if (!systemd_can_create($user_tab, $tab->{'unituser'}));
my $create_url = $user_tab && $tab->{'unituser'} ?
"edit_unit.cgi?new=1&scope=user&unittype=service&unituser=".
urlize($tab->{'unituser'}) :

View File

@@ -29,7 +29,7 @@ if ($in{'return'}) {
# Convert raw checkbox values into validated action records.
my @units = mass_units(\@sel, $user_scope, $users_scope, $unituser);
foreach my $u (@units) {
systemd_can_view_scope(\%access, $u->{'user_scope'}, $u->{'user'}) ||
systemd_can_view_scope($u->{'user_scope'}, $u->{'user'}) ||
systemd_acl_error($u->{'user_scope'} ? 'pview_user' : 'pview');
}
@@ -68,7 +68,7 @@ ui_print_unbuffered_header(undef, $logs ? $text{'systemd_logs'} :
if ($status) {
# Show full systemd status output for selected units.
foreach my $u (@units) {
systemd_can_inspect(\%access, $u->{'user_scope'}, $u->{'user'}) ||
systemd_can_inspect($u->{'user_scope'}, $u->{'user'}) ||
systemd_acl_error('pstatus');
my $s = $u->{'name'};
@@ -88,7 +88,7 @@ if ($status) {
if ($props) {
# Show the exact property set systemd reports for selected units.
foreach my $u (@units) {
systemd_can_inspect(\%access, $u->{'user_scope'}, $u->{'user'}) ||
systemd_can_inspect($u->{'user_scope'}, $u->{'user'}) ||
systemd_acl_error('pstatus');
my $s = $u->{'name'};
@@ -108,7 +108,7 @@ if ($props) {
if ($deps) {
# Show the dependency tree for selected units.
foreach my $u (@units) {
systemd_can_inspect(\%access, $u->{'user_scope'}, $u->{'user'}) ||
systemd_can_inspect($u->{'user_scope'}, $u->{'user'}) ||
systemd_acl_error('pstatus');
my $s = $u->{'name'};
@@ -128,7 +128,7 @@ if ($deps) {
if ($logs) {
# Show recent journal output for selected units.
foreach my $u (@units) {
systemd_can_logs(\%access, $u->{'user_scope'}, $u->{'user'}) ||
systemd_can_logs($u->{'user_scope'}, $u->{'user'}) ||
systemd_acl_error('plogs');
my $s = $u->{'name'};
@@ -149,7 +149,7 @@ if ($stop || $restart) {
# Webmin itself cannot be stopped here, but it can be restarted specially.
$SIG{'TERM'} = 'ignore'; # Restarting webmin may kill this script
foreach my $u (@units) {
systemd_can_runtime(\%access, $stop ? 'stop' : 'restart',
systemd_can_runtime($stop ? 'stop' : 'restart',
$u->{'user_scope'}, $u->{'user'}) ||
systemd_acl_error($stop ? 'pstop' : 'prestart');
my $s = $u->{'name'};
@@ -202,7 +202,7 @@ if ($stop || $restart) {
if ($enable || $disable) {
# Enable or disable startup for each selected unit.
foreach my $u (@units) {
systemd_can_boot(\%access, $u->{'user_scope'}, $u->{'user'}) ||
systemd_can_boot($u->{'user_scope'}, $u->{'user'}) ||
systemd_acl_error('pboot');
my $b = $u->{'name'};
my ($ok, $out) = (1, undef);
@@ -242,7 +242,7 @@ if ($enable || $disable) {
if ($mask || $unmask) {
# Masking prevents activation; unmasking restores normal start behavior.
foreach my $u (@units) {
systemd_can_mask(\%access, $u->{'user_scope'}, $u->{'user'}) ||
systemd_can_mask($u->{'user_scope'}, $u->{'user'}) ||
systemd_acl_error('pmask');
my $b = $u->{'name'};
my ($ok, $out);
@@ -275,7 +275,7 @@ if ($delete) {
# deletion stays on the per-unit edit page where the risk is clearer.
foreach my $u (@units) {
$u->{'user_scope'} || error($text{'mass_edelete_user'});
systemd_can_delete(\%access, 1, $u->{'user'}) ||
systemd_can_delete(1, $u->{'user'}) ||
systemd_acl_error('pdelete_user');
my $s = $u->{'name'};
print_action_start(text('mass_udeleting',
@@ -292,7 +292,7 @@ if ($delete) {
if ($start) {
# Start last, so "enable and start" first creates the wanted symlink.
foreach my $u (@units) {
systemd_can_runtime(\%access, 'start',
systemd_can_runtime('start',
$u->{'user_scope'}, $u->{'user'}) ||
systemd_acl_error('pstart');
my $s = $u->{'name'};

View File

@@ -10,7 +10,7 @@ our (%access, %text);
ReadParse();
error_setup($text{'reload_err'});
systemd_can_reload(\%access) || systemd_acl_error('preload');
systemd_can_reload() || systemd_acl_error('preload');
ui_print_unbuffered_header(undef, $text{'reload_title'}, "");

View File

@@ -14,7 +14,7 @@ error_setup($text{'reload_user_err'});
my $user = clean_unit_value($in{'user'});
my $uinfo = $user ? get_user_details($user) : undef;
$uinfo || error($text{'systemd_euser'});
systemd_can_reload_user(\%access, $uinfo->{'user'}) ||
systemd_can_reload_user($uinfo->{'user'}) ||
systemd_acl_error('pmanual_user');
ui_print_unbuffered_header(undef, $text{'reload_user_title'}, "");

View File

@@ -14,7 +14,7 @@ error_setup($text{'manual_err'});
# The posted path must still be in the discovered allowlist at save time.
my $info = manual_unit_file($in{'file'});
$info || error($text{'manual_efile'});
systemd_can_manual(\%access, $info) ||
systemd_can_manual($info) ||
systemd_acl_error($info->{'scope'} eq 'user' ?
'pmanual_user' : 'pmanual');
my ($ok, $err) = write_manual_unit_file($info, $in{'data'});

View File

@@ -25,13 +25,13 @@ if ($user_scope) {
# User units must always be tied to a real Unix account.
get_user_details($unituser) ||
error($text{'systemd_euser'});
systemd_can_view_scope(\%access, 1, $unituser) ||
systemd_can_view_scope(1, $unituser) ||
systemd_acl_error('pview_user');
@units = list_user_units($unituser);
}
else {
# System units are managed through the system manager.
systemd_can_view_scope(\%access, 0) || systemd_acl_error('pview');
systemd_can_view_scope(0) || systemd_acl_error('pview');
@units = list_units();
}
@@ -67,26 +67,26 @@ if (!$in{'new'} &&
($in{'start'} || $in{'stop'} || $in{'restart'} || $in{'status'} ||
$in{'props'} || $in{'deps'} || $in{'logs'})) {
if ($in{'start'}) {
systemd_can_runtime(\%access, 'start',
systemd_can_runtime('start',
$user_scope, $unituser) ||
systemd_acl_error('pstart');
}
elsif ($in{'stop'}) {
systemd_can_runtime(\%access, 'stop',
systemd_can_runtime('stop',
$user_scope, $unituser) ||
systemd_acl_error('pstop');
}
elsif ($in{'restart'}) {
systemd_can_runtime(\%access, 'restart',
systemd_can_runtime('restart',
$user_scope, $unituser) ||
systemd_acl_error('prestart');
}
elsif ($in{'logs'}) {
systemd_can_logs(\%access, $user_scope, $unituser) ||
systemd_can_logs($user_scope, $unituser) ||
systemd_acl_error('plogs');
}
else {
systemd_can_inspect(\%access, $user_scope, $unituser) ||
systemd_can_inspect($user_scope, $unituser) ||
systemd_acl_error('pstatus');
}
# Stream runtime actions through mass_units.cgi.
@@ -112,7 +112,7 @@ if (!$in{'new'} &&
if ($in{'override'}) {
# Create the standard override file if needed, then open that drop-in.
systemd_can_dropin(\%access, $user_scope, $unituser) ||
systemd_can_dropin($user_scope, $unituser) ||
systemd_acl_error($user_scope ? 'pdropin_user' : 'pdropin');
unit_file_editable($u) || error($text{'systemd_ereadonly'});
my $base_data = $user_scope ?
@@ -166,7 +166,7 @@ if ($in{'override'}) {
}
elsif ($in{'delete_override'}) {
# Drop-in deletes are available only from the override editor.
systemd_can_dropin(\%access, $user_scope, $unituser) ||
systemd_can_dropin($user_scope, $unituser) ||
systemd_acl_error($user_scope ? 'pdropin_user' : 'pdropin');
$edit_dropin || error($text{'systemd_edropinfile'});
$dropin_file && error($text{'systemd_edropinfile'});
@@ -192,7 +192,7 @@ elsif ($in{'delete_override'}) {
}
elsif ($in{'delete'}) {
# Delete the unit after trying to stop it and remove it from startup.
systemd_can_delete(\%access, $user_scope, $unituser) ||
systemd_can_delete($user_scope, $unituser) ||
systemd_acl_error($user_scope ? 'pdelete_user' : 'pdelete');
if ($user_scope) {
# User-unit deletion goes through helpers that drop to the owner.
@@ -215,7 +215,7 @@ elsif ($in{'delete'}) {
$redirect = index_url($in{'name'}, $user_scope, $unituser);
}
elsif ($in{'new'}) {
systemd_can_create(\%access, $user_scope, $user_scope ? $unituser : undef) ||
systemd_can_create($user_scope, $user_scope ? $unituser : undef) ||
systemd_acl_error($user_scope ? 'pcreate_user' : 'pcreate');
# Normalize the unit name and suffix before checking for clashes.
my @creatable_unit_types = get_creatable_unit_types($user_scope);
@@ -613,7 +613,7 @@ elsif ($in{'new'}) {
if ($user_scope) {
# Linger is optional on create, but enabling it also starts the manager.
if ($in{'linger'}) {
systemd_can_linger(\%access, $unituser) ||
systemd_can_linger($unituser) ||
systemd_acl_error('plinger');
my ($lok, $lout) = set_user_linger($unituser, 1);
$lok || error_user_command($unituser, $lout);
@@ -635,7 +635,7 @@ elsif ($in{'new'}) {
# Enable or disable startup after the unit has been written and reloaded.
if (defined($in{'boot'}) &&
systemd_can_boot(\%access, $user_scope, $unituser)) {
systemd_can_boot($user_scope, $unituser)) {
if ($user_scope) {
my ($ok, $out);
@@ -688,8 +688,8 @@ elsif ($in{'new'}) {
else {
# Save the raw unit file contents from the edit form.
my $can_save_unit = $edit_dropin ?
systemd_can_dropin(\%access, $user_scope, $unituser) :
systemd_can_edit(\%access, $user_scope, $unituser);
systemd_can_dropin($user_scope, $unituser) :
systemd_can_edit($user_scope, $unituser);
$can_save_unit ||
systemd_acl_error($edit_dropin ?
($user_scope ? 'pdropin_user' : 'pdropin') :
@@ -740,7 +740,7 @@ else {
# Enabling linger happens before reload; disabling waits until after.
if (defined($in{'linger'})) {
systemd_can_linger(\%access, $unituser) ||
systemd_can_linger($unituser) ||
systemd_acl_error('plinger');
if ($in{'linger'}) {
my ($lok, $lout) =
@@ -789,7 +789,7 @@ else {
# Apply startup state changes after saving the config.
if (defined($in{'boot'}) &&
boot_state_changeable($u->{'unitstate'}, $u->{'name'})) {
systemd_can_boot(\%access, $user_scope, $unituser) ||
systemd_can_boot($user_scope, $unituser) ||
systemd_acl_error('pboot');
if ($user_scope) {
my ($ok, $out);

View File

@@ -16,7 +16,7 @@ ReadParse();
my $user = clean_unit_value($in{'user'});
my $enabled = $in{'enabled'};
get_user_details($user) || error($text{'systemd_euser'});
systemd_can_linger(\%access, $user) || systemd_acl_error('plinger');
systemd_can_linger($user) || systemd_acl_error('plinger');
if (!defined($enabled) || ($enabled ne '0' && $enabled ne '1')) {
error($text{'systemd_elinger'});
}

View File

@@ -82,15 +82,27 @@ $acl{'uidmax'} = "";
return %acl;
}
=head2 systemd_acl_bool(&acl, key)
=head2 systemd_acl_args([&acl], args...)
Returns an ACL hash ref and the remaining arguments, defaulting to the current
module C<%access> hash when no explicit ACL is passed.
=cut
sub systemd_acl_args
{
my @args = @_;
my $acl = @args && ref($args[0]) eq 'HASH' ? shift(@args) : \%access;
return ($acl, @args);
}
=head2 systemd_acl_bool([&acl], key)
Returns a boolean ACL value.
=cut
sub systemd_acl_bool
{
my ($acl, $key) = @_;
$acl ||= \%access;
my ($acl, $key) = systemd_acl_args(@_);
return $acl->{$key} ? 1 : 0 if (exists($acl->{$key}));
return 0;
}
@@ -110,21 +122,21 @@ my $msg = $text{'eacl_'.$reason} || $text{'eacl_penter'} ||
error($prefix." ".$msg);
}
=head2 systemd_acl_any(&acl, keys...)
=head2 systemd_acl_any([&acl], keys...)
Returns 1 if any named ACL key is allowed.
=cut
sub systemd_acl_any
{
my ($acl, @keys) = @_;
my ($acl, @keys) = systemd_acl_args(@_);
foreach my $key (@keys) {
return 1 if (systemd_acl_bool($acl, $key));
}
return 0;
}
=head2 systemd_acl_user_allowed(&acl, user)
=head2 systemd_acl_user_allowed([&acl], user)
Returns 1 if the ACL's Unix-user filter permits access to a systemd user
manager owned by C<user>. The filter intentionally mirrors Cron's mode/users
@@ -133,8 +145,7 @@ ACL model so Virtualmin templates can grant per-owner access predictably.
=cut
sub systemd_acl_user_allowed
{
my ($acl, $user) = @_;
$acl ||= \%access;
my ($acl, $user) = systemd_acl_args(@_);
return 0 if (!$user);
my $mode = defined($acl->{'mode'}) ? $acl->{'mode'} : 0;
$mode = 0 if ($mode !~ /^[0-5]$/);
@@ -164,7 +175,7 @@ elsif ($mode == 5) {
return 1;
}
=head2 systemd_acl_default_user(&acl)
=head2 systemd_acl_default_user([&acl])
Returns a safe default Unix owner for user-unit views when the ACL narrows the
user set to exactly one account, or to the current Webmin user.
@@ -172,8 +183,7 @@ user set to exactly one account, or to the current Webmin user.
=cut
sub systemd_acl_default_user
{
my ($acl) = @_;
$acl ||= \%access;
my ($acl) = systemd_acl_args(@_);
my $mode = defined($acl->{'mode'}) ? $acl->{'mode'} : 0;
$mode = 0 if ($mode !~ /^[0-5]$/);
if ($mode == 1) {
@@ -191,19 +201,19 @@ elsif ($mode == 3) {
return;
}
=head2 systemd_can_view_system(&acl)
=head2 systemd_can_view_system([&acl])
Returns 1 if the ACL allows seeing or acting on system-scope units.
=cut
sub systemd_can_view_system
{
my ($acl) = @_;
my ($acl) = systemd_acl_args(@_);
return systemd_acl_any($acl, qw(view status logs start stop restart boot mask
create edit delete dropin manual reload));
}
=head2 systemd_can_view_user_scope(&acl, [user])
=head2 systemd_can_view_user_scope([&acl], [user])
Returns 1 if the ACL allows seeing or acting on user-scope units, optionally
constrained to a specific Unix owner.
@@ -211,7 +221,7 @@ constrained to a specific Unix owner.
=cut
sub systemd_can_view_user_scope
{
my ($acl, $user) = @_;
my ($acl, $user) = systemd_acl_args(@_);
return 0 if (defined($user) && $user ne "" &&
!systemd_acl_user_allowed($acl, $user));
return systemd_acl_bool($acl, 'view_user') ||
@@ -221,56 +231,56 @@ return systemd_acl_bool($acl, 'view_user') ||
linger));
}
=head2 systemd_can_enter_module(&acl)
=head2 systemd_can_enter_module([&acl])
Returns 1 if the ACL allows any interactive access to this module.
=cut
sub systemd_can_enter_module
{
my ($acl) = @_;
my ($acl) = systemd_acl_args(@_);
return systemd_can_view_system($acl) || systemd_can_view_user_scope($acl);
}
=head2 systemd_can_view_scope(&acl, user-scope, [user])
=head2 systemd_can_view_scope([&acl], user-scope, [user])
Returns 1 if the ACL allows seeing or acting on the selected unit scope.
=cut
sub systemd_can_view_scope
{
my ($acl, $user_scope, $user) = @_;
my ($acl, $user_scope, $user) = systemd_acl_args(@_);
return $user_scope ? systemd_can_view_user_scope($acl, $user) :
systemd_can_view_system($acl);
}
=head2 systemd_can_inspect(&acl, user-scope, [user])
=head2 systemd_can_inspect([&acl], user-scope, [user])
Returns 1 if status, properties or dependency inspection is allowed.
=cut
sub systemd_can_inspect
{
my ($acl, $user_scope, $user) = @_;
my ($acl, $user_scope, $user) = systemd_acl_args(@_);
my $key = $user_scope ? 'status_user' : 'status';
return systemd_acl_bool($acl, $key) &&
systemd_can_view_scope($acl, $user_scope, $user) ? 1 : 0;
}
=head2 systemd_can_logs(&acl, user-scope, [user])
=head2 systemd_can_logs([&acl], user-scope, [user])
Returns 1 if journal log inspection is allowed.
=cut
sub systemd_can_logs
{
my ($acl, $user_scope, $user) = @_;
my ($acl, $user_scope, $user) = systemd_acl_args(@_);
my $key = $user_scope ? 'logs_user' : 'logs';
return systemd_acl_bool($acl, $key) &&
systemd_can_view_scope($acl, $user_scope, $user) ? 1 : 0;
}
=head2 systemd_can_runtime(&acl, action, user-scope, [user])
=head2 systemd_can_runtime([&acl], action, user-scope, [user])
Returns 1 if a runtime action such as C<start>, C<stop> or C<restart> is
allowed for the selected scope.
@@ -278,92 +288,92 @@ allowed for the selected scope.
=cut
sub systemd_can_runtime
{
my ($acl, $action, $user_scope, $user) = @_;
my ($acl, $action, $user_scope, $user) = systemd_acl_args(@_);
return 0 if ($action !~ /^(start|stop|restart)$/);
my $key = $user_scope ? $action.'_user' : $action;
return systemd_acl_bool($acl, $key) &&
systemd_can_view_scope($acl, $user_scope, $user) ? 1 : 0;
}
=head2 systemd_can_boot(&acl, user-scope, [user])
=head2 systemd_can_boot([&acl], user-scope, [user])
Returns 1 if enabling, disabling, masking or unmasking units is allowed.
=cut
sub systemd_can_boot
{
my ($acl, $user_scope, $user) = @_;
my ($acl, $user_scope, $user) = systemd_acl_args(@_);
my $key = $user_scope ? 'boot_user' : 'boot';
return systemd_acl_bool($acl, $key) &&
systemd_can_view_scope($acl, $user_scope, $user) ? 1 : 0;
}
=head2 systemd_can_mask(&acl, user-scope, [user])
=head2 systemd_can_mask([&acl], user-scope, [user])
Returns 1 if masking or unmasking units is allowed.
=cut
sub systemd_can_mask
{
my ($acl, $user_scope, $user) = @_;
my ($acl, $user_scope, $user) = systemd_acl_args(@_);
my $key = $user_scope ? 'mask_user' : 'mask';
return systemd_acl_bool($acl, $key) &&
systemd_can_view_scope($acl, $user_scope, $user) ? 1 : 0;
}
=head2 systemd_can_create(&acl, user-scope, [user])
=head2 systemd_can_create([&acl], user-scope, [user])
Returns 1 if creating units in the selected scope is allowed.
=cut
sub systemd_can_create
{
my ($acl, $user_scope, $user) = @_;
my ($acl, $user_scope, $user) = systemd_acl_args(@_);
my $key = $user_scope ? 'create_user' : 'create';
return systemd_acl_bool($acl, $key) &&
systemd_can_view_scope($acl, $user_scope, $user) ? 1 : 0;
}
=head2 systemd_can_edit(&acl, user-scope, [user])
=head2 systemd_can_edit([&acl], user-scope, [user])
Returns 1 if editing a full unit file in the selected scope is allowed.
=cut
sub systemd_can_edit
{
my ($acl, $user_scope, $user) = @_;
my ($acl, $user_scope, $user) = systemd_acl_args(@_);
my $key = $user_scope ? 'edit_user' : 'edit';
return systemd_acl_bool($acl, $key) &&
systemd_can_view_scope($acl, $user_scope, $user) ? 1 : 0;
}
=head2 systemd_can_delete(&acl, user-scope, [user])
=head2 systemd_can_delete([&acl], user-scope, [user])
Returns 1 if deleting units in the selected scope is allowed.
=cut
sub systemd_can_delete
{
my ($acl, $user_scope, $user) = @_;
my ($acl, $user_scope, $user) = systemd_acl_args(@_);
my $key = $user_scope ? 'delete_user' : 'delete';
return systemd_acl_bool($acl, $key) &&
systemd_can_view_scope($acl, $user_scope, $user) ? 1 : 0;
}
=head2 systemd_can_dropin(&acl, user-scope, [user])
=head2 systemd_can_dropin([&acl], user-scope, [user])
Returns 1 if managing drop-in overrides in the selected scope is allowed.
=cut
sub systemd_can_dropin
{
my ($acl, $user_scope, $user) = @_;
my ($acl, $user_scope, $user) = systemd_acl_args(@_);
my $key = $user_scope ? 'dropin_user' : 'dropin';
return systemd_acl_bool($acl, $key) &&
systemd_can_view_scope($acl, $user_scope, $user) ? 1 : 0;
}
=head2 systemd_can_manual(&acl, file-info)
=head2 systemd_can_manual([&acl], file-info)
Returns 1 if the ACL permits manual editing for a file descriptor returned by
C<list_manual_unit_files>.
@@ -371,7 +381,7 @@ C<list_manual_unit_files>.
=cut
sub systemd_can_manual
{
my ($acl, $info) = @_;
my ($acl, $info) = @_ == 1 ? (\%access, $_[0]) : @_;
return 0 if (!$info || !$info->{'scope'});
if ($info->{'scope'} eq 'user') {
return systemd_acl_bool($acl, 'manual_user') &&
@@ -380,37 +390,37 @@ if ($info->{'scope'} eq 'user') {
return systemd_acl_bool($acl, 'manual') ? 1 : 0;
}
=head2 systemd_can_linger(&acl, user)
=head2 systemd_can_linger([&acl], user)
Returns 1 if managing linger for a Unix user is allowed.
=cut
sub systemd_can_linger
{
my ($acl, $user) = @_;
my ($acl, $user) = systemd_acl_args(@_);
return systemd_acl_bool($acl, 'linger') &&
systemd_acl_user_allowed($acl, $user) ? 1 : 0;
}
=head2 systemd_can_reload(&acl)
=head2 systemd_can_reload([&acl])
Returns 1 if reloading the system manager is allowed.
=cut
sub systemd_can_reload
{
my ($acl) = @_;
my ($acl) = systemd_acl_args(@_);
return systemd_acl_bool($acl, 'reload');
}
=head2 systemd_can_reload_user(&acl, user)
=head2 systemd_can_reload_user([&acl], user)
Returns 1 if reloading a user's systemd manager is allowed.
=cut
sub systemd_can_reload_user
{
my ($acl, $user) = @_;
my ($acl, $user) = systemd_acl_args(@_);
return 0 if (!systemd_can_view_user_scope($acl, $user));
return systemd_acl_any($acl, qw(create_user edit_user delete_user
dropin_user manual_user boot_user
@@ -3161,7 +3171,7 @@ sub action_reload_user
my ($user) = @_;
$user ||= defined($in{'unituser'}) ? clean_unit_value($in{'unituser'}) : "";
$user ||= defined($in{'user'}) ? clean_unit_value($in{'user'}) : "";
$user ||= systemd_acl_default_user(\%access) || "";
$user ||= systemd_acl_default_user() || "";
my $uinfo = $user ? get_user_details($user) : undef;
return $uinfo ? $uinfo->{'user'} : undef;
}
@@ -3177,13 +3187,13 @@ my ($user) = @_;
my @links;
push(@links, ui_link("restart.cgi",
ui_tag('b', html_escape($text{'index_reload'}))))
if (needs_daemon_reload() && systemd_can_reload(\%access));
if (needs_daemon_reload() && systemd_can_reload());
my $reload_user = action_reload_user($user);
push(@links, ui_link("restart_user.cgi?user=".urlize($reload_user),
ui_tag('b', html_escape($text{'index_reload_user'}))))
if ($reload_user &&
needs_user_daemon_reload($reload_user) &&
systemd_can_reload_user(\%access, $reload_user));
systemd_can_reload_user($reload_user));
return join(" &nbsp; ", @links);
}

View File

@@ -242,6 +242,17 @@ ok(!unit_file_editable({
'start', 1),
'one runtime action does not grant another');
{
local %access = ( view_user => 1, create_user => 1,
linger => 1, mode => 1, users => 'alice' );
ok(systemd_acl_bool('view_user'),
'ACL bool defaults to the current module ACL');
ok(systemd_can_create(1, 'alice'),
'ACL helpers default to the current module ACL');
ok(!systemd_can_create(1, 'bob'),
'default ACL still applies user ownership filters');
}
my %only_acl = ( mode => 1, users => 'alice bob' );
ok(systemd_acl_user_allowed(\%only_acl, 'alice'),
'user ACL only-list accepts listed owner');
@@ -2030,7 +2041,7 @@ like($index_source, qr/sub index_tabs\b/,
'index contains tab builder helper');
like($index_source, qr/sub index_tab_groups\b/,
'index defines grouped tabs for related unit types');
like($index_source, qr/next if \(!systemd_can_view_system\(\\%access\)\)/,
like($index_source, qr/next if \(!systemd_can_view_system\(\)\)/,
'index shows system tabs only when system scope is allowed');
unlike($index_source, qr/next if \(!\@\$units\)/,
'index keeps visible tabs even when they have no units');