From f2fe6c930f2eeed58c625386be34d2923e499ee0 Mon Sep 17 00:00:00 2001 From: Ilia Ross Date: Mon, 22 Jun 2026 02:14:10 +0200 Subject: [PATCH] Fix to change systemd ACL helper calls https://github.com/webmin/webmin/pull/2760#discussion_r3449183293 --- systemd/backup_config.pl | 4 +- systemd/dropins.cgi | 14 +++--- systemd/edit_manual.cgi | 12 ++--- systemd/edit_unit.cgi | 44 ++++++++-------- systemd/index.cgi | 44 ++++++++-------- systemd/mass_units.cgi | 20 ++++---- systemd/restart.cgi | 2 +- systemd/restart_user.cgi | 2 +- systemd/save_manual.cgi | 2 +- systemd/save_unit.cgi | 34 ++++++------- systemd/set_linger.cgi | 2 +- systemd/systemd-lib.pl | 106 +++++++++++++++++++++------------------ systemd/t/run-tests.t | 13 ++++- 13 files changed, 158 insertions(+), 141 deletions(-) diff --git a/systemd/backup_config.pl b/systemd/backup_config.pl index 425c6b4b1..f6a1551c5 100644 --- a/systemd/backup_config.pl +++ b/systemd/backup_config.pl @@ -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)) { diff --git a/systemd/dropins.cgi b/systemd/dropins.cgi index 735814772..609ec3e68 100755 --- a/systemd/dropins.cgi +++ b/systemd/dropins.cgi @@ -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'}); diff --git a/systemd/edit_manual.cgi b/systemd/edit_manual.cgi index 1ae201149..f98facc04 100755 --- a/systemd/edit_manual.cgi +++ b/systemd/edit_manual.cgi @@ -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'}; } diff --git a/systemd/edit_unit.cgi b/systemd/edit_unit.cgi index 65f641932..82c3db864 100755 --- a/systemd/edit_unit.cgi +++ b/systemd/edit_unit.cgi @@ -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'} ]); } diff --git a/systemd/index.cgi b/systemd/index.cgi index 2e66e4be4..4fea73f2f 100755 --- a/systemd/index.cgi +++ b/systemd/index.cgi @@ -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'}) : diff --git a/systemd/mass_units.cgi b/systemd/mass_units.cgi index 0919555aa..c5e935ea9 100755 --- a/systemd/mass_units.cgi +++ b/systemd/mass_units.cgi @@ -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'}; diff --git a/systemd/restart.cgi b/systemd/restart.cgi index 34023fcd8..5f2f2b07f 100755 --- a/systemd/restart.cgi +++ b/systemd/restart.cgi @@ -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'}, ""); diff --git a/systemd/restart_user.cgi b/systemd/restart_user.cgi index 5016e32b3..de110e65d 100755 --- a/systemd/restart_user.cgi +++ b/systemd/restart_user.cgi @@ -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'}, ""); diff --git a/systemd/save_manual.cgi b/systemd/save_manual.cgi index 6dd3aa89c..d7a0d663d 100755 --- a/systemd/save_manual.cgi +++ b/systemd/save_manual.cgi @@ -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'}); diff --git a/systemd/save_unit.cgi b/systemd/save_unit.cgi index 8b1b945bf..438a10a72 100755 --- a/systemd/save_unit.cgi +++ b/systemd/save_unit.cgi @@ -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); diff --git a/systemd/set_linger.cgi b/systemd/set_linger.cgi index 2d54fef9e..98b45b0f2 100755 --- a/systemd/set_linger.cgi +++ b/systemd/set_linger.cgi @@ -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'}); } diff --git a/systemd/systemd-lib.pl b/systemd/systemd-lib.pl index 4549ccd80..d6f466967 100644 --- a/systemd/systemd-lib.pl +++ b/systemd/systemd-lib.pl @@ -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. 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, C or C 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. @@ -371,7 +381,7 @@ C. =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("   ", @links); } diff --git a/systemd/t/run-tests.t b/systemd/t/run-tests.t index 896b4ebbf..27b5a1615 100644 --- a/systemd/t/run-tests.t +++ b/systemd/t/run-tests.t @@ -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');