Compare commits

..

12 Commits

Author SHA1 Message Date
Jamie Cameron
ef1fb66954 Merge pull request #2657 from swelljoe/security-scan-core
Some checks are pending
webmin.dev: webmin/webmin / build (push) Waiting to run
typo s/actphost/acpthost/
2026-04-07 06:43:02 -07:00
Joe Cooper
316529c8ca typo s/actphost/acpthost/ 2026-04-06 22:27:52 -05:00
Jamie Cameron
d1e72c08a0 Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-06 08:01:56 -07:00
Jamie Cameron
7ed4763f00 Don't declare a filesystem to be too small if we don't know how small it is
https://github.com/webmin/webmin/issues/2653
2026-04-06 08:01:19 -07:00
Ilia Ross
fe90fb2479 Fix process nice level and IO controls switchable
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/2652

[no-build]
2026-04-03 16:43:19 +02:00
Jamie Cameron
e44a25191f Add support for headers with multiple values
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-02 07:31:51 -07:00
Jamie Cameron
3dbeb4e4db Don't trust proxy-provided SSL cert if it's flagged as not verified
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-01 18:17:02 -07:00
Jamie Cameron
7c5d4087fc Quote params by default 2026-04-01 18:07:19 -07:00
Ilia Ross
83e4fed616 Fix to correctly preserve full quoted action params in Fail2Ban jail editor #2647 2026-04-02 00:48:13 +02:00
Jamie Cameron
5b8dba4a5f Obsolete scripts that no longer need to exist
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-01 12:49:21 -07:00
Jamie Cameron
2404e1ddfc Permissions update
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-29 22:28:47 -07:00
Jamie Cameron
3a75f0f3f8 Sometimes ntfs is in lower case
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/2635
2026-03-29 21:19:03 -07:00
19 changed files with 182 additions and 204 deletions

View File

@@ -1,5 +1,8 @@
## Changelog
#### 2.631 (April, 2026)
* Fix to correctly preserve full quoted action parameters in the Fail2Ban jail editor [#2647](https://github.com/webmin/webmin/issues/2647)
#### 2.630 (March 24, 2026)
* Add improvements to user input validation across all modules
* Update Authentic theme to the latest version with various improvements and fixes:

0
bsdfdisk/change_slice_label.cgi Normal file → Executable file
View File

0
bsdfdisk/save_slice_label.cgi Normal file → Executable file
View File

0
bsdfdisk/zfs_create.cgi Normal file → Executable file
View File

0
bsdfdisk/zvol_create.cgi Normal file → Executable file
View File

View File

@@ -341,7 +341,7 @@ local $ptable = &ui_columns_start([
$noquote ? ( ) : ( $text{'edit_quote'} ),
$text{'edit_must'},
], 100, 0, undef, undef);
local @a = (@{$cmd->{'args'}}, { });
local @a = (@{$cmd->{'args'}}, { 'quote' => 1 });
for(my $i=0; $i<@a; $i++) {
local @cols;
push(@cols, &ui_textbox("name_$i", $a[$i]->{'name'}, 10));

View File

@@ -83,18 +83,12 @@ my $atable = &ui_columns_start([
]);
my $i = 0;
foreach my $a (@{$actionlist->{'words'}}, undef) {
my $action;
my %opts;
if ($a && $a =~ /^(\S.*\S)\[(.*)\]$/) {
$action = $1;
%opts = map { my ($n, $v) = split(/=/, $_);
$v =~ s/^"(.*)"/$1/;
($n, $v) } split(/,\s+/, $2);
}
else {
$action = $a;
}
my @oopts = grep { !/^(name|port|protocol)$/ } (keys %opts);
my ($action, $opts) = $a ? &parse_action_definition($a) : (undef, []);
my %opts = map { ($_->[0], $_->[1]) } @$opts;
my @oopts = grep { $_->[0] !~ /^(name|port|protocol)$/ } @$opts;
my $others = join("", map { $_->[2] } @oopts);
$others =~ s/^\s*,?\s*//;
$others =~ s/\s*,?\s*$//;
$atable .= &ui_columns_row([
&ui_select("action_$i", $action,
[ [ "", "&nbsp;" ],
@@ -107,8 +101,7 @@ foreach my $a (@{$actionlist->{'words'}}, undef) {
[ 'tcp', 'TCP' ],
[ 'udp', 'UDP' ],
[ 'icmp', 'ICMP' ] ]),
&ui_textbox("others_$i",
join(" ", map { $_."=".$opts{$_} } @oopts), 40),
&ui_textbox("others_$i", $others, 40),
]);
$i++;
}

View File

@@ -222,6 +222,122 @@ while($v =~ /\S/) {
$dir->{'words'} = \@w;
}
# parse_action_definition(string)
# Splits an action definition into a name and list of option triples,
# each containing a name, parsed value and raw text chunk
sub parse_action_definition
{
my ($str) = @_;
return (undef, []) if (!defined($str));
if ($str =~ /^(\S.*\S)\[(.*)\]$/) {
return ($1, [ &parse_action_options($2) ]);
}
return ($str, []);
}
# parse_action_options(string)
# Parses action options, handling quoted values and comma/space separators
sub parse_action_options
{
my ($str) = @_;
my @rv;
my $len = length($str);
my $i = 0;
while ($i < $len) {
while ($i < $len) {
my $ch = substr($str, $i, 1);
last if ($ch !~ /[\s,]/);
$i++;
}
last if ($i >= $len);
my $start = $i;
my $name = "";
while ($i < $len) {
my $ch = substr($str, $i, 1);
last if ($ch eq "=" || $ch =~ /[\s,]/);
$name .= $ch;
$i++;
}
last if (!length($name));
while ($i < $len && substr($str, $i, 1) =~ /\s/) {
$i++;
}
# Support option names without values, although most action
# parameters are expected to be name=value pairs.
if ($i >= $len || substr($str, $i, 1) ne "=") {
while ($i < $len && substr($str, $i, 1) =~ /\s/) {
$i++;
}
$i++ if ($i < $len && substr($str, $i, 1) eq ",");
push(@rv, [ $name, undef, substr($str, $start, $i - $start) ]);
next;
}
$i++;
while ($i < $len && substr($str, $i, 1) =~ /\s/) {
$i++;
}
my ($value, $ni) = &parse_action_option_value($str, $i);
push(@rv, [ $name, $value, substr($str, $start, $ni - $start) ]);
$i = $ni;
}
return @rv;
}
# parse_action_option_value(string, index)
# Returns an option value and the next parse position
sub parse_action_option_value
{
my ($str, $i) = @_;
my $len = length($str);
my $value = "";
if ($i < $len) {
my $quote = substr($str, $i, 1);
if ($quote eq "'" || $quote eq "\"") {
$i++;
while ($i < $len) {
my $ch = substr($str, $i, 1);
if ($ch eq "\\") {
if ($i+1 < $len) {
my $next = substr($str, $i+1, 1);
if ($next eq $quote || $next eq "\\") {
$value .= $next;
$i += 2;
next;
}
}
$value .= $ch;
$i++;
next;
}
if ($ch eq $quote) {
$i++;
last;
}
$value .= $ch;
$i++;
}
}
else {
while ($i < $len) {
my $ch = substr($str, $i, 1);
last if ($ch eq "," || $ch =~ /\s/);
$value .= $ch;
$i++;
}
}
}
while ($i < $len && substr($str, $i, 1) =~ /\s/) {
$i++;
}
$i++ if ($i < $len && substr($str, $i, 1) eq ",");
return ($value, $i);
}
# create_section(file, &section)
# Add a new section to a file
sub create_section

View File

@@ -28,7 +28,7 @@ foreach my $j (@jails) {
my $action_dir = &find("action", $j);
my $action = "";
if ($action_dir) {
$action = join("&nbsp;|&nbsp;",
$action = join(",&nbsp;",
map { /^([^\[]+)/; &html_escape("$1") }
@{$action_dir->{'words'}});
}

View File

@@ -85,12 +85,15 @@ else {
if ($in{"protocol_$i"}) {
push(@opts, "protocol=".$in{"protocol_$i"});
}
foreach my $oo (split(/\s+/, $in{"others_$i"})) {
my ($n, $v) = split(/=/, $oo, 2);
$v = "\"$v\"" if ($v =~ /\s|,|=/ && $v !~ /['"]/);
push(@opts, "$n=$v");
if ($in{"others_$i"}) {
my $others = $in{"others_$i"};
$others =~ s/^\s+//;
$others =~ s/\s+$//;
push(@opts, $others) if (length($others));
}
push(@actions, $in{"action_$i"}."[".join(", ", @opts)."]");
my $action = $in{"action_$i"};
$action .= "[".join(", ", @opts)."]" if (@opts);
push(@actions, $action);
}
# Split and validate log file paths

View File

@@ -1602,6 +1602,7 @@ else { return " $_[2] ".quotemeta($in{$_[0]}); }
'HFS', 'MacOS HFS',
'linux-swap', 'Linux Swap',
'NTFS', 'Windows NTFS',
'ntfs', 'Windows NTFS',
'reiserfs', 'ReiserFS',
'ufs', 'FreeBSD UFS',
);

View File

@@ -1721,7 +1721,8 @@ if ($header{'user-agent'} =~ /webmin/i ||
# Check for SSL authentication
my $trust_ssl = $config{'trust_real_ip'} && !$config{'no_trust_ssl'};
if ($use_ssl && $verified_client ||
$trust_ssl && $header{'x-ssl-client-dn'}) {
$trust_ssl && $header{'x-ssl-client-dn'} &&
$header{'x-ssl-client-verifiy'} !~ /^(failed|none)/i) {
if ($use_ssl && $verified_client) {
$peername = Net::SSLeay::X509_NAME_oneline(
Net::SSLeay::X509_get_subject_name(
@@ -1817,7 +1818,7 @@ if ($config{'session'} && !$deny_authentication &&
}
&run_logout_script($louser, $sid,
$loghost, $localip);
&write_logout_utmp($louser, $actphost);
&write_logout_utmp($louser, $acpthost);
}
}
elsif ($in{'session'}) {

View File

@@ -91,7 +91,7 @@ if (!$gconfig{'tempdir'} && &foreign_available("webmin")) {
foreach my $disk (sort { length($b->{'dir'}) <=>
length($a->{'dir'}) } @$disks) {
if (&is_under_directory($disk->{'dir'}, $tmp)) {
if ($disk->{'total'} <= $small) {
if ($disk->{'total'} && $disk->{'total'} <= $small) {
# Too small
push(@rv, { 'type' => 'warning',
'level' => 'info',

View File

@@ -14,6 +14,8 @@ if (!%pinfo) {
exit;
}
print &ui_form_start("renice_proc.cgi");
print &ui_hidden("pid", $ARGV[0]);
print &ui_table_start($text{'edit_title'}, "width=100%", 4,
[ "width=20%", "width=30%", "width=20%", "width=30%" ]);
@@ -49,16 +51,29 @@ print &ui_table_row($text{'size'}, $pinfo{'bytes'} ? &nice_size($pinfo{'bytes'})
print &ui_table_row($text{'runtime'}, $pinfo{'time'});
# Nice level
print &ui_form_start("renice_proc.cgi");
print &ui_hidden("pid", $ARGV[0]);
local $nice_value = $pinfo{'nice'};
$nice_value =~ s/^\s+// if (defined($nice_value));
$nice_value =~ s/\s+$// if (defined($nice_value));
$nice_value =~ s/^\+// if (defined($nice_value));
local $submitjs =
"onchange='this.parentNode.getElementsByTagName(\"input\")[0].click()'";
local $l = scalar(@nice_range);
print &ui_table_row(&hlink($text{'nice'},"nice"),
&indexof($pinfo{nice}, @nice_range) < 0 ? $pinfo{nice} :
&nice_selector("nice", $pinfo{nice}).
&ui_submit($text{'proc_submit'}), 3);
&ui_select("nice", $nice_value,
[ map { [ $_, $_.($_ == $nice_range[0] ? " ($text{'edit_prihigh'})" :
$_ == 0 ? " ($text{'edit_pridef'})" :
$_ == $nice_range[$l-1]
? " ($text{'edit_prilow'})"
: "") ] } @nice_range ],
1, 0, 1, 0, $submitjs).
&ui_submit("", "nice_submit_hidden", 0, "style='display:none'"), 3);
# IO scheduling class, if support
if (defined(&os_list_scheduling_classes) &&
(@classes = &os_list_scheduling_classes())) {
local @classes;
local ($class, $prio);
local $has_sched = defined(&os_list_scheduling_classes) &&
(@classes = &os_list_scheduling_classes());
if ($has_sched) {
($class, $prio) = &os_get_scheduling_class($pinfo{'pid'});
($got) = grep { $_->[0] == $class } @classes;
if (!$got) {
@@ -66,14 +81,18 @@ if (defined(&os_list_scheduling_classes) &&
unshift(@classes, [ $class, $text{'default'} ]);
}
print &ui_table_row(&hlink($text{'sclass'},"sclass"),
&ui_select("sclass", $class, \@classes));
&ui_select("sclass", $class, \@classes, 1, 0, 0, 0,
$submitjs).
&ui_submit("", "sclass_submit_hidden", 0,
"style='display:none'"));
print &ui_table_row(&hlink($text{'sprio'},"sprio"),
&ui_select("sprio", $prio,
[ &os_list_scheduling_priorities() ], 1, 0, 1));
[ &os_list_scheduling_priorities() ], 1, 0, 1, 0,
$submitjs).
&ui_submit("", "sprio_submit_hidden", 0,
"style='display:none'"));
}
print &ui_form_end();
# Extra OS-specific info
foreach $k (keys %pinfo) {
if ($k =~ /^_/ && $info_arg_map{$k}) {
@@ -81,6 +100,7 @@ foreach $k (keys %pinfo) {
}
}
print &ui_table_end();
print &ui_form_end();
print "<table width=100%><tr>\n";
if ($access{'simple'}) {

View File

@@ -1,58 +0,0 @@
#!/usr/local/bin/perl
# edit_ffile.cgi
# Allow editing of a filter config file
require './qmail-lib.pl';
&ReadParse();
&ui_print_header(undef, $text{'ffile_title'}, "");
open(FILE, "<$in{'file'}");
while(<FILE>) {
s/\r|\n//g;
if (/^(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/) {
push(@filter, [ $1, $2, $3, $4 ]);
}
elsif (/^(2)\s+(\S+)$/) {
$other = $2;
}
}
close(FILE);
print "<b>",&text('ffile_desc', "<tt>$in{'file'}</tt>"),"</b><p>\n";
print "<form action=save_ffile.cgi method=post enctype=multipart/form-data>\n";
print "<input type=hidden name=file value=\"$in{'file'}\">\n";
print "<input type=hidden name=name value=\"$in{'name'}\">\n";
$i = 0;
foreach $f (@filter, [ 1, '', '', '' ]) {
$field = "<select name=field_$i>\n";
foreach $ft ('', 'from', 'to', 'subject', 'cc', 'body') {
$field .= sprintf "<option value='%s' %s>%s</option>\n",
$ft, $f->[2] eq $ft ? "selected" : "",
$ft ? $text{"ffile_$ft"} : "&nbsp";
}
$field .= "</select>\n";
$what = "<select name=what_$i>\n";
$what .= sprintf "<option value=0 %s>%s</option>\n",
$f->[0] == 0 ? "selected" : "", $text{"ffile_what0"};
$what .= sprintf "<option value=1 %s>%s</option>\n",
$f->[0] == 1 ? "selected" : "", $text{"ffile_what1"};
$what .= "</select>\n";
$match = "<input name=match_$i size=20 value='$f->[3]'>\n";
$action = "<input name=action_$i size=30 value='$f->[1]'>\n";
print &text('ffile_line', $field, $what, $match, $action),"<br>\n";
$i++;
}
print &text('ffile_other',
"<input name=other size=30 value='$other'>"),"<br>\n";
print "<input type=submit value=\"$text{'save'}\">\n";
print "</form>\n";
&ui_print_footer("edit_alias.cgi?name=$in{'name'}", $text{'aform_return'});

View File

@@ -1,53 +0,0 @@
#!/usr/local/bin/perl
# edit_rfile.cgi
# Display the contents of an autoreply file
require './qmail-lib.pl';
&ReadParse();
&ui_print_header(undef, $text{'rfile_title'}, "");
open(FILE, "<$in{'file'}");
while(<FILE>) {
if (/^Reply-Tracking:\s*(.*)/) {
$replies = $1;
}
elsif (/^Reply-Period:\s*(.*)/) {
$period = $1;
}
else {
push(@lines, $_);
}
}
close(FILE);
print "<b>",&text('rfile_desc', "<tt>$in{'file'}</tt>"),"</b><p>\n";
print "$text{'rfile_desc2'}<p>\n";
print "<form action=save_rfile.cgi method=post enctype=multipart/form-data>\n";
print "<input type=hidden name=file value=\"$in{'file'}\">\n";
print "<input type=hidden name=name value=\"$in{'name'}\">\n";
print "<textarea name=text rows=20 cols=80 $config{'wrap_mode'}>",
join("", @lines),"</textarea><p>\n";
print $text{'rfile_replies'},"\n";
printf "<input type=radio name=replies_def value=1 %s> %s\n",
$replies eq '' ? "checked" : "", $text{'rfile_none'};
printf "<input type=radio name=replies_def value=0 %s> %s\n",
$replies eq '' ? "" :"checked", $text{'rfile_file'};
printf "<input name=replies size=30 value='%s'> %s<br>\n",
$replies, &file_chooser_button("replies");
print "&nbsp;" x 3;
print $text{'rfile_period'},"\n";
printf "<input type=radio name=period_def value=1 %s> %s\n",
$period eq '' ? "checked" : "", $text{'rfile_default'};
printf "<input type=radio name=period_def value=0 %s>\n",
$period eq '' ? "" :"checked";
printf "<input name=period size=5 value='%s'> %s<p>\n",
$period, $text{'rfile_secs'};
print "<input type=submit value=\"$text{'save'}\"> ",
"<input type=reset value=\"$text{'rfile_undo'}\">\n";
print "</form>\n";
&ui_print_footer("edit_alias.cgi?name=$in{'name'}", $text{'aform_return'});

View File

@@ -1,27 +0,0 @@
#!/usr/local/bin/perl
# save_afile.cgi
# Save a filter file
require './qmail-lib.pl';
&ReadParseMime();
&error_setup($text{'ffile_err'});
my %access = &get_module_acl();
my $base = &simplify_path($access{'apath'} || $qmail_alias_dir);
my $file = &simplify_path($in{'file'});
&is_under_directory($base, $file) || &error(&text('ffile_efile', $in{'file'}));
$in{'file'} = $file;
for($i=0; defined($in{"field_$i"}); $i++) {
next if (!$in{"field_$i"});
$in{"match_$i"} || &error($text{'ffile_ematch'});
$in{"action_$i"} || &error($text{'ffile_eaction'});
push(@filter, $in{"what_$i"}." ".$in{"action_$i"}." ".
$in{"field_$i"}." ".$in{"match_$i"}."\n");
}
push(@filter, "2 ".$in{'other'}."\n") if ($in{'other'});
&open_lock_tempfile(FILE, ">$in{'file'}");
&print_tempfile(FILE, @filter);
&close_tempfile(FILE);
&redirect("edit_alias.cgi?name=$in{'name'}");

View File

@@ -1,29 +0,0 @@
#!/usr/local/bin/perl
# save_rfile.cgi
# Save an autoreply file
require './qmail-lib.pl';
&ReadParseMime();
my %access = &get_module_acl();
my $base = &simplify_path($access{'apath'} || $qmail_alias_dir);
my $file = &simplify_path($in{'file'});
&is_under_directory($base, $file) || &error(&text('rfile_efile', $in{'file'}));
$in{'file'} = $file;
$in{'replies_def'} || $in{'replies'} =~ /^\/\S+/ ||
&error($text{'rfile_ereplies'});
$in{'period_def'} || $in{'period'} =~ /^\d+$/ ||
&error($text{'rfile_eperiod'});
$in{'text'} =~ s/\r//g;
&open_lock_tempfile(FILE, ">$in{'file'}");
if (!$in{'replies_def'}) {
&print_tempfile(FILE, "Reply-Tracking: $in{'replies'}\n");
}
if (!$in{'period_def'}) {
&print_tempfile(FILE, "Reply-Period: $in{'period'}\n");
}
&print_tempfile(FILE, $in{'text'});
&close_tempfile(FILE);
&redirect("edit_alias.cgi?name=$in{'name'}");

View File

@@ -3179,7 +3179,11 @@ if ($user) {
push(@headers, [ "Authorization", "Basic $auth" ]);
}
foreach my $hname (keys %$headers) {
push(@headers, [ $hname, $headers->{$hname} ]);
my $hv = $headers->{$hname};
$hv = [ $hv ] if (!ref($hv));
foreach my $v (@$hv) {
push(@headers, [ $hname, $v ]);
}
}
# Actually download it
@@ -3393,7 +3397,11 @@ if ($user) {
}
@headers = grep { !$headers->{$_->[0]} } @headers;
foreach my $hname (keys %$headers) {
push(@headers, [ $hname, $headers->{$hname} ]);
my $hv = $headers->{$hname};
$hv = [ $hv ] if (!ref($hv));
foreach my $v (@$hv) {
push(@headers, [ $hname, $v ]);
}
}
# Actually download it