mirror of
https://github.com/webmin/webmin.git
synced 2026-10-01 01:40:39 +01:00
Merge branch 'master' of github.com:webmin/webmin
This commit is contained in:
11
CHANGELOG.md
11
CHANGELOG.md
@@ -1,4 +1,8 @@
|
||||
## Changelog
|
||||
#### 2.671 (October, 2026)
|
||||
* Fix two-factor authentication validation to use the Webmin configuration and data directories passed by the caller
|
||||
* Fix HTML escaping of module, script and session fields on the Webmin Actions Log details page
|
||||
|
||||
#### 2.670 (September 20, 2026)
|
||||
* Add new Hardware Information module for inspecting system, firmware, security, PCI, USB, storage, network, processor, sensor, driver, and kernel module details
|
||||
* Add options to send Webmin and Usermin errors to the systemd journal [forum.virtualmin.com/t/136562](https://forum.virtualmin.com/t/miniserv-webserver-log-growing-too-big-should-be-rotated/136562)
|
||||
@@ -20,6 +24,13 @@
|
||||
* Fix ACL operations bypassing configured allowed paths in File Manager
|
||||
* Fix arbitrary file reads in Software Packages module
|
||||
* Fix TLS client certificate verification
|
||||
* Fix Apache Webserver module to enforce the allowed directory ACL when manually editing `.htaccess` files
|
||||
* Fix Custom Commands module to properly quote parameter values passed to commands and to reject parameters with multiple values
|
||||
* Fix DNSSEC NSEC3 zone signing in BIND DNS Server module to use zero additional hash iterations, as recommended by RFC 9276
|
||||
* Fix System Logs module to create a new log file only after the log entry passes validation
|
||||
* Fix file chooser to lift the root directory restriction only when the chroot is at or under the allowed root
|
||||
* Fix password changes from the login page using a configured password change command to fail unless the command prompts for the old password
|
||||
* Fix File Manager search results to only include files under the allowed directories
|
||||
* Update Backup Configuration module's destination selector to use the new select-based UI
|
||||
* Update the Authentic theme to the latest version with various improvements:
|
||||
- Add support for HTML signatures in Usermin
|
||||
|
||||
2
lang/en
2
lang/en
@@ -314,7 +314,7 @@ password_enew2=Your new passwords do not match
|
||||
password_epam=PAM error : $1
|
||||
password_emodpam=The Authen::PAM perl module needed to do password changes is not installed!
|
||||
password_enewpass=New password is not allowed : $1
|
||||
password_enotold=Password change commit did not prompt for old password!
|
||||
password_enotold=Password change command did not prompt for old password!
|
||||
|
||||
ui_mandatory=This field is mandatory
|
||||
ui_checkmandatory=Nothing was selected
|
||||
|
||||
@@ -585,10 +585,11 @@ my ($name) = @_;
|
||||
return (0, $text{'systemd_ename'}) if (!valid_unit_name($name));
|
||||
my $out = backquote_logged(
|
||||
"systemctl start ".quotemeta($name)." 2>&1 </dev/null");
|
||||
if ($? && $out =~ /journalctl/) {
|
||||
my $rv = $?;
|
||||
if ($rv && $out =~ /journalctl/) {
|
||||
$out .= backquote_command("journalctl -xe 2>/dev/null");
|
||||
}
|
||||
return (!$?, $out);
|
||||
return (!$rv, $out);
|
||||
}
|
||||
|
||||
=head2 stop_unit(name)
|
||||
@@ -661,6 +662,33 @@ my $out = backquote_logged(
|
||||
return (!$?, $out);
|
||||
}
|
||||
|
||||
=head2 get_unit_state(name)
|
||||
|
||||
Returns a fresh hash of LoadState, UnitFileState, ActiveState, SubState and
|
||||
MainPID, followed by an error string. Missing units have LoadState not-found;
|
||||
command or validation failures return undef and an error. No inventory cache
|
||||
is used, so callers can verify a change immediately.
|
||||
|
||||
=cut
|
||||
sub get_unit_state
|
||||
{
|
||||
my ($name) = @_;
|
||||
return (undef, $text{'systemd_ename'}) if (!valid_unit_name($name));
|
||||
my $out = backquote_logged("systemctl show --no-pager ".
|
||||
"--property=LoadState,UnitFileState,ActiveState,SubState,MainPID ".
|
||||
quotemeta($name)." 2>&1 </dev/null");
|
||||
my $rv = $?;
|
||||
my %state;
|
||||
foreach my $line (split(/\r?\n/, $out)) {
|
||||
$state{$1} = $2
|
||||
if ($line =~ /^(LoadState|UnitFileState|ActiveState|SubState|MainPID)=(.*)$/);
|
||||
}
|
||||
# systemctl may return a nonzero status for an explicitly missing unit.
|
||||
return (undef, $out || "Failed to read systemd unit $name")
|
||||
if (!$state{'LoadState'} || ($rv && $state{'LoadState'} ne 'not-found'));
|
||||
return (\%state, undef);
|
||||
}
|
||||
|
||||
=head2 dependencies_unit(name)
|
||||
|
||||
Gets dependency tree output for a systemd unit.
|
||||
|
||||
@@ -846,6 +846,39 @@ like(get_unit_root(), qr{^/(etc|usr/lib|lib)/systemd/system$},
|
||||
'dependency command uses full non-paged output');
|
||||
}
|
||||
|
||||
# Failed starts must remain failures even when journal diagnostics succeed.
|
||||
{
|
||||
local *main::backquote_logged = sub { $? = 256; return 'See journalctl for details'; };
|
||||
local *main::backquote_command = sub { $? = 0; return 'Start failed'; };
|
||||
my ($ok, $out) = start_unit('broken.service');
|
||||
ok(!$ok, 'journal lookup cannot hide a failed start');
|
||||
like($out, qr/Start failed/, 'failed start includes diagnostics');
|
||||
}
|
||||
|
||||
# Readiness needs fresh properties, including idle sockets and missing units.
|
||||
{
|
||||
my $reply = "LoadState=loaded\nUnitFileState=enabled\nActiveState=active\nSubState=listening\nMainPID=0\n";
|
||||
my $status = 0;
|
||||
my @commands;
|
||||
local *main::backquote_logged = sub { push @commands, $_[0]; $? = $status; return $reply; };
|
||||
my ($state, $error) = get_unit_state('demo.socket');
|
||||
is($error, undef, 'valid unit state has no error');
|
||||
is($state->{SubState}, 'listening', 'socket readiness is exposed');
|
||||
like($commands[-1], qr/--property=LoadState,UnitFileState,ActiveState,SubState,MainPID/, 'only requested properties are read');
|
||||
$reply = "LoadState=loaded\nUnitFileState=enabled\nActiveState=inactive\n";
|
||||
($state, $error) = get_unit_state('demo.socket');
|
||||
is($state->{ActiveState}, 'inactive', 'state is not cached');
|
||||
$reply = "LoadState=not-found\nActiveState=inactive\n"; $status = 1024;
|
||||
($state, $error) = get_unit_state('missing.service');
|
||||
is($state->{LoadState}, 'not-found', 'missing unit is a valid discovery result');
|
||||
$reply = 'Failed to connect to bus';
|
||||
($state, $error) = get_unit_state('demo.socket');
|
||||
ok(!defined($state), 'bus failure returns no state');
|
||||
like($error, qr/connect to bus/, 'bus error is preserved');
|
||||
($state, $error) = get_unit_state('bad;unit.service');
|
||||
is($error, 'bad unit name', 'invalid state lookup is rejected');
|
||||
}
|
||||
|
||||
{
|
||||
my @cmds;
|
||||
my $reloaded = 0;
|
||||
|
||||
49
t/web-lib-funcs-miniserv-config.t
Normal file
49
t/web-lib-funcs-miniserv-config.t
Normal file
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/perl
|
||||
# Concurrent WebSocket updates must not reuse a same-second config snapshot.
|
||||
use strict;
|
||||
use warnings;
|
||||
use Test::More;
|
||||
use File::Temp qw(tempdir);
|
||||
use File::Basename qw(dirname);
|
||||
use File::Spec;
|
||||
|
||||
require File::Spec->rel2abs(dirname(__FILE__).'/../web-lib-funcs.pl');
|
||||
my $directory = tempdir(CLEANUP => 1);
|
||||
my $file = "$directory/miniserv.conf";
|
||||
{
|
||||
no warnings qw(redefine once);
|
||||
*main::get_miniserv_config_file = sub { return $file; };
|
||||
}
|
||||
|
||||
# replace_config(contents) simulates another process writing within one second.
|
||||
sub replace_config
|
||||
{
|
||||
my ($contents) = @_;
|
||||
open(my $fh, '>', $file) or die $!;
|
||||
print {$fh} $contents;
|
||||
close($fh) or die $!;
|
||||
utime(1700000000, 1700000000, $file) or die $!;
|
||||
}
|
||||
|
||||
replace_config("port=10000\nwebsockets_/test/ws-555=first\n");
|
||||
my %first;
|
||||
ok(main::get_miniserv_config(\%first), 'read initial configuration');
|
||||
is($first{'websockets_/test/ws-555'}, 'first', 'first route is present');
|
||||
|
||||
# Allocation must retain a route added since the caller's initial config read.
|
||||
replace_config("port=10000\nwebsockets_/test/ws-555=first\n".
|
||||
"websockets_/test/ws-556=second\n");
|
||||
my %added;
|
||||
main::get_miniserv_config(\%added);
|
||||
is($added{'websockets_/test/ws-556'}, 'second',
|
||||
'same-second route addition is visible');
|
||||
|
||||
# Cleanup must not restore a route another backend has already removed.
|
||||
replace_config("port=10000\nwebsockets_/test/ws-556=second\n");
|
||||
my %removed;
|
||||
main::get_miniserv_config(\%removed);
|
||||
ok(!exists($removed{'websockets_/test/ws-555'}),
|
||||
'same-second route removal is visible');
|
||||
is($removed{'websockets_/test/ws-556'}, 'second',
|
||||
'unrelated route survives cleanup');
|
||||
done_testing();
|
||||
@@ -3083,7 +3083,9 @@ hash reference.
|
||||
=cut
|
||||
sub get_miniserv_config
|
||||
{
|
||||
return &read_file_cached_with_stat(&get_miniserv_config_file(), $_[0]);
|
||||
# WebSocket backends can change routes several times within one second.
|
||||
# An mtime-based cache can lose another process's update even under a file lock.
|
||||
return &read_file(&get_miniserv_config_file(), $_[0]);
|
||||
}
|
||||
|
||||
=head2 put_miniserv_config(&hash)
|
||||
|
||||
Reference in New Issue
Block a user