mirror of
https://github.com/webmin/webmin.git
synced 2026-04-06 17:20:27 +01:00
Compare commits
32 Commits
dev/no-log
...
2.630
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2b4fa89c5 | ||
|
|
3a3b202a96 | ||
|
|
30f08f73fb | ||
|
|
e499b5b3a5 | ||
|
|
443cf449eb | ||
|
|
58d6308589 | ||
|
|
02ed8e8fbd | ||
|
|
3a0dea1d2c | ||
|
|
916d22b55b | ||
|
|
1f1a7e4562 | ||
|
|
ac68a0be0c | ||
|
|
263cc142a6 | ||
|
|
19fdea395b | ||
|
|
40f82d0df3 | ||
|
|
39ab2c5f02 | ||
|
|
f39a59bdce | ||
|
|
a4846f5f32 | ||
|
|
5558910722 | ||
|
|
bebd99d656 | ||
|
|
bad0d2f821 | ||
|
|
4797852f6f | ||
|
|
d467810076 | ||
|
|
6d1ec1a3e1 | ||
|
|
82ea895c81 | ||
|
|
36e699eb29 | ||
|
|
04e8df863a | ||
|
|
96c2312349 | ||
|
|
1d594e82f0 | ||
|
|
cbd96a4176 | ||
|
|
ed17ade510 | ||
|
|
dc63aa22a5 | ||
|
|
1b9b9ae21f |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,5 +1,15 @@
|
||||
## Changelog
|
||||
|
||||
#### 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:
|
||||
- Add a new airy button style to the light palette to match the dark one
|
||||
- Fix to optimize stats server to reduce WebSocket memory usage
|
||||
- Fix the real-time follow indicator when viewing the journal
|
||||
- Fix regex-based match highlighting when viewing the journal
|
||||
- Fix mail compose panel sizing in HTML mode on low-DPR screens
|
||||
- Fix display of the 2FA QR code in the dark palette
|
||||
|
||||
#### 2.621 (January 25, 2026)
|
||||
* Fix to prevent NAT from dropping idle RPC sessions during long transfers
|
||||
* Fix to improve the message when socket authentication is used in the MySQL/MariaDB module
|
||||
|
||||
@@ -66,14 +66,18 @@ if ($in{'enable'}) {
|
||||
{ 'provider' => $user->{'twofactor_provider'},
|
||||
'id' => $user->{'twofactor_id'} });
|
||||
|
||||
# Show a test form, so the user can validate
|
||||
print &ui_form_start("test_twofactor.cgi");
|
||||
print $text{'twofactor_testdesc'},"<p>\n";
|
||||
print "$text{'twofactor_testfield'} \n",
|
||||
&ui_textbox("test", undef, 12),"\n";
|
||||
print &ui_hidden("user", $in{'user'}) if ($in{'user'});
|
||||
print "<p>\n";
|
||||
print &ui_form_end([ [ undef, $text{'twofactor_test'} ] ]);
|
||||
# Show a test form only when enrolling for yourself
|
||||
if ($user->{'name'} eq $base_remote_user) {
|
||||
print &ui_form_start("test_twofactor.cgi");
|
||||
print &ui_tag('p', $text{'twofactor_testdesc'});
|
||||
print &ui_tag('p', "$text{'twofactor_testfield'}".
|
||||
" ".
|
||||
&ui_textbox("test", undef, 12));
|
||||
print &ui_hidden("user", $in{'user'}) if ($in{'user'});
|
||||
print &ui_tag('p');
|
||||
print &ui_form_end([ [ undef,
|
||||
$text{'twofactor_test'} ] ]);
|
||||
}
|
||||
}
|
||||
|
||||
&ui_print_footer("", $text{'index_return'});
|
||||
|
||||
@@ -60,12 +60,11 @@ print &ui_tabs_end_tab();
|
||||
|
||||
# Show immediate form
|
||||
print &ui_tabs_start_tab("tab", "backup");
|
||||
my $filename = 'webmin-backup-config-on-';
|
||||
my $hostname = &get_system_hostname();
|
||||
my $hostname = &get_system_hostname() || "localhost";
|
||||
$hostname =~ s/\./-/g;
|
||||
$filename .= $hostname;
|
||||
$filename .= "-".strftime("%Y-%m-%d-%H-%M", localtime);
|
||||
print &ui_form_start("backup.cgi/$filename.tgz", "post");
|
||||
my $filename = $hostname."+configuration_backup-webmin-".
|
||||
strftime("%Y-%m-%d-%H-%M", localtime);
|
||||
print &ui_form_start("backup.cgi/$filename.tar.gz", "post");
|
||||
print &ui_table_start($text{'index_header'}, undef, 2);
|
||||
|
||||
my @dmods = split(/\s+/, $config{'mods'} || "");
|
||||
|
||||
@@ -700,7 +700,7 @@ if ($v && $v->{'members'}) {
|
||||
push(@av, join(" ", $av->{'name'}, @{$av->{'values'}}));
|
||||
}
|
||||
}
|
||||
if ($_[3] == 0) {
|
||||
if (!$_[3]) {
|
||||
# text area
|
||||
return &ui_table_row($_[0],
|
||||
&ui_textarea($_[1], join("\n", @av), 3, 50));
|
||||
@@ -805,14 +805,14 @@ my $v = &find($_[1], $_[2]);
|
||||
my $n;
|
||||
($n = $_[1]) =~ s/[^A-Za-z0-9_]/_/g;
|
||||
return &ui_table_row($_[0],
|
||||
&ui_opt_textbox($n, $v ? $v->{'value'} : "", $_[4], $_[3])." ".$_[5],
|
||||
&ui_opt_textbox($n, $v ? $v->{'value'} : "", $_[4], $_[3])." ".($_[5] // ""),
|
||||
$_[4] > 30 ? 3 : 1);
|
||||
}
|
||||
|
||||
sub save_opt
|
||||
{
|
||||
my ($dir, $n, $err);
|
||||
($n = $_[0]) =~ s/[^A-Za-z0-9_]/_/g;
|
||||
($n = ($_[0] // "")) =~ s/[^A-Za-z0-9_]/_/g;
|
||||
if ($in{"${n}_def"}) { &save_directive($_[2], $_[0], [ ], $_[3]); }
|
||||
elsif ($err = &{$_[1]}($in{$n})) {
|
||||
&error($err);
|
||||
@@ -906,7 +906,7 @@ my ($fwdconf, $fwdfile, $fwdrec, $ipv6);
|
||||
# find forward domain
|
||||
my $host = $_[0]; $host =~ s/\.$//;
|
||||
my @zl = grep { $_->{'type'} ne 'view' } &list_zone_names();
|
||||
if ($_[1] ne '' && $_[1] ne 'any') {
|
||||
if ($_[1] && $_[1] ne 'any') {
|
||||
@zl = grep { $_->{'view'} && $_->{'viewindex'} == $_[1] } @zl;
|
||||
}
|
||||
else {
|
||||
@@ -968,6 +968,7 @@ else {
|
||||
# Returns 1 if some zone can be edited
|
||||
sub can_edit_zone
|
||||
{
|
||||
$access{'zones'} //= '*';
|
||||
my %zcan;
|
||||
my ($zn, $vn, $file);
|
||||
if ($_[0]->{'members'}) {
|
||||
@@ -2405,7 +2406,7 @@ return undef;
|
||||
sub is_bind_running
|
||||
{
|
||||
my $pidfile = &get_pid_file();
|
||||
my $rv = &check_pid_file(&make_chroot($pidfile, 1));
|
||||
my $rv = &check_pid_file(&make_chroot($pidfile, 1)) || 0;
|
||||
if (!$rv && $gconfig{'os_type'} eq 'windows') {
|
||||
# Fall back to checking for process
|
||||
$rv = &find_byname("named");
|
||||
@@ -2536,6 +2537,7 @@ if ($changed || !$znc{'version'} ||
|
||||
next if (!$type);
|
||||
$type = lc($type);
|
||||
my $file = &find_value("file", $z->{'members'});
|
||||
$file //= "";
|
||||
my $up = &find("update-policy", $z->{'members'});
|
||||
my $au = &find("allow-update", $z->{'members'});
|
||||
my $dynamic = $up || $au || $gau ? 1 : 0;
|
||||
@@ -3198,6 +3200,7 @@ else {
|
||||
$zonename = $zone->{'name'};
|
||||
$zonefile = $zone->{'file'};
|
||||
}
|
||||
return () if (!$zonename || !$zonefile);
|
||||
my $out = &backquote_command(
|
||||
$config{'checkzone'}." ".quotemeta($zonename)." ".
|
||||
quotemeta(&make_chroot(&absolute_path($zonefile)))." 2>&1 </dev/null");
|
||||
@@ -3221,6 +3224,7 @@ else {
|
||||
$zonename = $zone->{'name'};
|
||||
$zonefile = $zone->{'file'};
|
||||
}
|
||||
return () if (!$zonename || !$zonefile);
|
||||
my $absfile = &make_chroot(&absolute_path($zonefile));
|
||||
my $out = &backquote_command(
|
||||
$config{'checkzone'}." ".quotemeta($zonename)." ".
|
||||
@@ -3363,7 +3367,7 @@ if (!$access{'ro'} && $access{'apply'}) {
|
||||
if ($zone && ($access{'apply'} == 1 || $access{'apply'} == 2)) {
|
||||
# Apply this zone
|
||||
my $link = "restart_zone.cgi?return=$r&".
|
||||
"view=$zone->{'viewindex'}&".
|
||||
"view=".($zone->{'viewindex'} // "")."&".
|
||||
"zone=$zone->{'name'}";
|
||||
push(@rv, &ui_link($link, $text{'links_apply'}) );
|
||||
}
|
||||
@@ -3934,7 +3938,7 @@ if (&find_byname("nscd")) {
|
||||
sub transfer_slave_records
|
||||
{
|
||||
my ($dom, $masters, $file, $source, $sourceport) = @_;
|
||||
my $sourcearg;
|
||||
my $sourcearg = "";
|
||||
if ($source && $source ne "*") {
|
||||
$sourcearg = "-t ".$source;
|
||||
if ($sourceport) {
|
||||
|
||||
@@ -28,12 +28,12 @@ for(my $i=0; $i<@servers; $i++) {
|
||||
my @cols = ( );
|
||||
push(@cols, &ui_textbox("ip_$i", $s->{'value'}, 30));
|
||||
|
||||
my $bogus = &find_value("bogus", $s->{'members'});
|
||||
my $bogus = &find_value("bogus", $s->{'members'}) // "";
|
||||
push(@cols, &ui_radio("bogus_$i", lc($bogus) eq 'yes' ? 1 : 0,
|
||||
[ [ 1, $text{'yes'} ],
|
||||
[ 0, $text{'no'} ] ]));
|
||||
|
||||
my $format = &find_value("transfer-format", $s->{'members'});
|
||||
my $format = &find_value("transfer-format", $s->{'members'}) // "";
|
||||
push(@cols, &ui_radio("format_$i", lc($format),
|
||||
[ [ 'one-answer', $text{'servers_one'} ],
|
||||
[ 'many-answers', $text{'servers_many'} ],
|
||||
|
||||
@@ -45,7 +45,7 @@ else {
|
||||
|
||||
my %bumpedrev;
|
||||
my @delr;
|
||||
foreach my $d (sort { $b <=> $a } @d) {
|
||||
foreach my $d (sort { ($b =~ /^(\d+)/)[0] <=> ($a =~ /^(\d+)/)[0] } @d) {
|
||||
my ($num, $id) = split(/\//, $d, 2);
|
||||
my $r = &find_record_by_id(\@recs, $id, $num);
|
||||
next if (!$r);
|
||||
@@ -77,7 +77,7 @@ else {
|
||||
# Delete the actual record
|
||||
&lock_file(&make_chroot($r->{'file'}));
|
||||
&delete_record($r->{'file'}, $r);
|
||||
splice(@recs, $d, 1);
|
||||
splice(@recs, $num, 1);
|
||||
push(@delr, $r);
|
||||
}
|
||||
&bump_soa_record($zone->{'file'}, \@recs);
|
||||
|
||||
@@ -172,6 +172,7 @@ for(my $i=0; $i<@_; $i++) {
|
||||
else {
|
||||
$name = $r->{'name'};
|
||||
}
|
||||
$name //= "";
|
||||
my @cols;
|
||||
$name = &html_escape($name);
|
||||
my $id = &record_id($r);
|
||||
|
||||
@@ -488,6 +488,7 @@ else {
|
||||
}
|
||||
else {
|
||||
# For other record types, just save the lines
|
||||
$in{'values'} //= "";
|
||||
$in{'values'} =~ s/\r//g;
|
||||
my @vlines = split(/\n/, $in{'values'});
|
||||
$vals = join(" ",map { $_ =~ /\s|;/ ? "\"$_\"" : $_ } @vlines);
|
||||
|
||||
@@ -918,7 +918,7 @@ if ($has_parted) {
|
||||
elsif ($tag eq "linux-swap") {
|
||||
@rv = ( "swap" );
|
||||
}
|
||||
elsif ($tag eq "NTFS") {
|
||||
elsif ($tag eq "NTFS" || $tag eq "ntfs") {
|
||||
@rv = ( "ntfs" );
|
||||
}
|
||||
elsif ($tag eq "reiserfs") {
|
||||
|
||||
@@ -4,6 +4,16 @@
|
||||
$no_acl_check++;
|
||||
require './fsdump-lib.pl';
|
||||
|
||||
sub start_tls
|
||||
{
|
||||
my ($fh, $what) = @_;
|
||||
eval { require IO::Socket::SSL; IO::Socket::SSL->import(); 1; } ||
|
||||
&error_exit("FTP server requires TLS, but IO::Socket::SSL is not installed");
|
||||
IO::Socket::SSL->start_SSL($fh, SSL_verify_mode => 0) ||
|
||||
&error_exit("FTP $what TLS handshake failed : ".
|
||||
IO::Socket::SSL::errstr());
|
||||
}
|
||||
|
||||
# Parse args, and get password
|
||||
select(STDERR); $| = 1; select(STDOUT);
|
||||
$host = $ARGV[0];
|
||||
@@ -36,6 +46,15 @@ while(1) {
|
||||
&error_exit("FTP connection failed : $err") if ($err);
|
||||
&ftp_command("", 2, \$err) ||
|
||||
&error_exit("FTP prompt failed : $err");
|
||||
$ssl_enabled = 0;
|
||||
if (&ftp_command("AUTH TLS", 2, \$err)) {
|
||||
&start_tls(\*SOCK, "control");
|
||||
&ftp_command("PBSZ 0", 2, \$err) ||
|
||||
&error_exit("FTP TLS setup failed : $err");
|
||||
&ftp_command("PROT P", 2, \$err) ||
|
||||
&error_exit("FTP TLS setup failed : $err");
|
||||
$ssl_enabled = 1;
|
||||
}
|
||||
|
||||
# Login to server
|
||||
@urv = &ftp_command("USER $user", [ 2, 3 ], \$err);
|
||||
@@ -174,5 +193,8 @@ elsif ($mode == 2) {
|
||||
else {
|
||||
$opened = 0;
|
||||
}
|
||||
if ($opened && $ssl_enabled) {
|
||||
&start_tls(\*CON, "data");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -430,8 +430,8 @@ if (!$main::ui_hidden_start_donejs++) {
|
||||
}
|
||||
|
||||
# Build list of tab titles and names
|
||||
my $tabnames = "[".join(",", map { "\""."e_escape($_->[0])."\"" } @$tabs)."]";
|
||||
my $tabtitles = "[".join(",", map { "\""."e_escape($_->[1])."\"" } @$tabs)."]";
|
||||
my $tabnames = &convert_to_json([map { $_->[0] } @$tabs]);
|
||||
my $tabtitles = &convert_to_json([map { $_->[1] } @$tabs]);
|
||||
$rv .= "<script>\n";
|
||||
$rv .= "document.${name}_tabnames = $tabnames;\n";
|
||||
$rv .= "document.${name}_tabtitles = $tabtitles;\n";
|
||||
@@ -649,7 +649,7 @@ if (!$main::WRAPPER_OPEN) { # If we're not already inside of a wrapper, wrap it
|
||||
}
|
||||
$main::WRAPPER_OPEN++;
|
||||
my $colspan = 1;
|
||||
$rv .= "<details class='ui_hidden_table_start'$opened>";
|
||||
$rv .= "<details data-name='$name' class='ui_hidden_table_start'$opened $tabletags>";
|
||||
$rv .= "<summary>$header $rheader</summary>\n";
|
||||
$rv .= "<table width=100%>\n";
|
||||
$main::ui_table_cols = $cols || 4;
|
||||
|
||||
@@ -1175,6 +1175,47 @@ elsif ($init_mode eq "launchd") {
|
||||
}
|
||||
}
|
||||
|
||||
=head2 activate_action(name)
|
||||
|
||||
Unmasks some action, enables it at boot time, and starts it if not running.
|
||||
Returns 1 if the action exists, 0 if not.
|
||||
|
||||
=cut
|
||||
sub activate_action
|
||||
{
|
||||
my ($name) = @_;
|
||||
my $st = &action_status($name);
|
||||
return 0 if (!$st);
|
||||
&unmask_action($name);
|
||||
&enable_at_boot($name);
|
||||
my $running = &status_action($name);
|
||||
if ($running != 1) { # unknown or stopped
|
||||
&start_action($name);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
=head2 deactivate_action(name, [mask])
|
||||
|
||||
Stops some action if currently running, disables it at boot time, and masks it
|
||||
on systemd systems. The optional mask flag can be set to 0 to skip masking.
|
||||
Returns 1 if the action exists, 0 if not.
|
||||
|
||||
=cut
|
||||
sub deactivate_action
|
||||
{
|
||||
my ($name, $mask) = @_;
|
||||
my $st = &action_status($name);
|
||||
return 0 if (!$st);
|
||||
my $running = &status_action($name);
|
||||
if ($running != 0) { # unknown or running
|
||||
&stop_action($name);
|
||||
}
|
||||
&disable_at_boot($name);
|
||||
&mask_action($name) if (!defined($mask) || $mask);
|
||||
return 1;
|
||||
}
|
||||
|
||||
=head2 delete_at_boot(name)
|
||||
|
||||
Delete the init script, RC script or whatever with some name
|
||||
|
||||
@@ -221,12 +221,23 @@ else {
|
||||
print &ui_table_row($text{'index_email'}, $efield);
|
||||
|
||||
# Install or just notify?
|
||||
print &ui_table_row($text{'index_action'},
|
||||
&ui_radio("action", int($config{'sched_action'}),
|
||||
[ [ -1, $text{'index_action-1'} ],
|
||||
[ 0, $text{'index_action0'} ],
|
||||
[ 1, $text{'index_action1'} ],
|
||||
[ 2, $text{'index_action2'} ] ]));
|
||||
$action_ui = &ui_select("action", int($config{'sched_action'}),
|
||||
[ [ -1, $text{'index_action-1'} ],
|
||||
[ 0, $text{'index_action0'} ],
|
||||
[ 1, $text{'index_action1'} ],
|
||||
[ 2, $text{'index_action2'} ] ]);
|
||||
if (my @auto_updates = &list_enabled_auto_update_services()) {
|
||||
# If any auto-update services are enabled, show option to disable them
|
||||
$auto_update_names = join(", ", map { $_->{'name'} } @auto_updates);
|
||||
$action_ui .= " ".
|
||||
&ui_checkbox("disable_auto_updates", 1,
|
||||
&text('index_action_disable',
|
||||
$auto_update_names), 0);
|
||||
$action_ui .= "<br>\n".
|
||||
&ui_note(&text('index_action_note',
|
||||
&ui_tag('tt', $auto_update_names)));
|
||||
}
|
||||
print &ui_table_row($text{'index_action'}, $action_ui);
|
||||
|
||||
print &ui_table_end();
|
||||
print &ui_form_end([ [ "save", $text{'save'} ] ]);
|
||||
|
||||
@@ -24,6 +24,8 @@ index_action-1=Just notify for security updates
|
||||
index_action0=Just notify for any updates
|
||||
index_action1=Install security updates
|
||||
index_action2=Install any updates
|
||||
index_action_note=Updates may also be installed by $1; disable external updates if you want Webmin to handle updates exclusively
|
||||
index_action_disable=Disable external updates
|
||||
index_err=Failed to fetch package list
|
||||
index_refresh=Refresh Available Packages
|
||||
index_noupdate=No update exists from version $1
|
||||
|
||||
@@ -8,6 +8,16 @@ eval "use WebminCore;";
|
||||
&foreign_require("cron", "cron-lib.pl");
|
||||
&foreign_require("webmin", "webmin-lib.pl");
|
||||
|
||||
# Known OS package auto-update services that can overlap with Webmin's
|
||||
# scheduled package updates.
|
||||
@auto_update_services = (
|
||||
"unattended-upgrades",
|
||||
"dnf-automatic.timer",
|
||||
"dnf-automatic-install.timer",
|
||||
"dnf-automatic-download.timer",
|
||||
"dnf-automatic-notifyonly.timer",
|
||||
);
|
||||
|
||||
$available_cache_file = &cache_file_path("available.cache");
|
||||
$current_cache_file = &cache_file_path("current.cache");
|
||||
$updates_cache_file = &cache_file_path("updates.cache");
|
||||
@@ -19,6 +29,36 @@ $yum_changelog_cache_dir = &cache_file_path("yumchangelog");
|
||||
|
||||
$update_progress_dir = "$module_var_directory/progress";
|
||||
|
||||
# list_enabled_auto_update_services()
|
||||
# Returns known OS-level auto-update services that are currently enabled.
|
||||
sub list_enabled_auto_update_services
|
||||
{
|
||||
return ( ) if (!&foreign_check("init"));
|
||||
&foreign_require("init");
|
||||
my @rv;
|
||||
foreach my $service (@auto_update_services) {
|
||||
next if (&init::action_status($service) != 2); # not enabled
|
||||
my $service_name = $service;
|
||||
$service_name =~ s/\.[^\.]+$//; # nice name
|
||||
push(@rv, { 'service' => $service, 'name' => $service_name });
|
||||
}
|
||||
return @rv;
|
||||
}
|
||||
|
||||
# disable_enabled_auto_update_services()
|
||||
# Stops, disables and masks known auto-update services that are enabled.
|
||||
sub disable_enabled_auto_update_services
|
||||
{
|
||||
return ( ) if (!&foreign_check("init"));
|
||||
&foreign_require("init");
|
||||
my @services = map { $_->{'service'} } &list_enabled_auto_update_services();
|
||||
my @rv;
|
||||
foreach my $service (@services) {
|
||||
push(@rv, $service) if (&init::deactivate_action($service, 0));
|
||||
}
|
||||
return @rv;
|
||||
}
|
||||
|
||||
# cache_file_path(name)
|
||||
# Returns a path in the /var directory unless the file already exists under
|
||||
# /etc/webmin
|
||||
|
||||
@@ -51,6 +51,11 @@ else {
|
||||
$msg = $text{'sched_yes'};
|
||||
}
|
||||
|
||||
# Disable auto-update services if requested
|
||||
if ($in{'disable_auto_updates'}) {
|
||||
&disable_enabled_auto_update_services();
|
||||
}
|
||||
|
||||
# Tell the user
|
||||
&ui_print_header(undef, $text{'sched_title'}, "");
|
||||
|
||||
@@ -59,4 +64,3 @@ print "$msg<p>\n";
|
||||
&webmin_log("sched", undef, $in{'sched_def'} ? 0 : 1);
|
||||
&ui_print_footer("index.cgi?mode=$in{'mode'}&search=".
|
||||
&urlize($in{'search'}), $text{'index_return'});
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ index_medit=Manage
|
||||
index_manual=Edit Manually
|
||||
index_anyfile=Edit other PHP configuration file
|
||||
index_return=configuration files
|
||||
index_pkgs=Manage PHP Packages
|
||||
index_pkgs=Manage PHP Versions
|
||||
index_pkgsdesc=Install and remove PHP versions from your system's software package repository, so that they can be configured here any used in Virtualmin.
|
||||
|
||||
file_global=Global PHP configuration
|
||||
|
||||
@@ -61,7 +61,7 @@ if (&foreign_installed("package-updates")) {
|
||||
my @allpkgs = &extend_installable_php_packages(\@newpkgs);
|
||||
@allpkgs = sort { $b->{'ver'} cmp $a->{'ver'} } @allpkgs;
|
||||
print &ui_select("u", undef,
|
||||
[ map { [ $_->{'name'}, "PHP $_->{'ver'}" ] } @allpkgs ]);
|
||||
[ map { [ $_->{'name'}, $_->{'ver'} ] } @allpkgs ]);
|
||||
print &ui_hidden(
|
||||
"redir", &get_webprefix()."/$module_name/list_pkgs.cgi");
|
||||
print &ui_hidden("redirdesc", $text{'pkgs_title'});
|
||||
|
||||
@@ -4,3 +4,5 @@ name=PostgreSQL
|
||||
longdesc=Manage databases, tables and users in your PostgreSQL database server.
|
||||
readonly=1
|
||||
cpan=1
|
||||
rpm_recommends=perl-DBI perl-DBD-Pg
|
||||
deb_recommends=libdbi-perl libdbd-pg-perl
|
||||
|
||||
@@ -6,7 +6,7 @@ require (-r 'sendmail-lib.pl' ? './sendmail-lib.pl' :
|
||||
-r 'qmail-lib.pl' ? './qmail-lib.pl' :
|
||||
'./postfix-lib.pl');
|
||||
&ReadParse();
|
||||
if (substr($in{'file'}, 0, length($access{'apath'})) ne $access{'apath'}) {
|
||||
if (!&is_under_directory($access{'apath'}, $in{'file'})) {
|
||||
&error(&text('afile_efile', $in{'file'}));
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ require (-r 'sendmail-lib.pl' ? './sendmail-lib.pl' :
|
||||
-r 'qmail-lib.pl' ? './qmail-lib.pl' :
|
||||
'./postfix-lib.pl');
|
||||
&ReadParse();
|
||||
if (substr($in{'file'}, 0, length($access{'apath'})) ne $access{'apath'}) {
|
||||
if (!&is_under_directory($access{'apath'}, $in{'file'})) {
|
||||
&error(&text('ffile_efile', $in{'file'}));
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ require (-r 'sendmail-lib.pl' ? './sendmail-lib.pl' :
|
||||
-r 'qmail-lib.pl' ? './qmail-lib.pl' :
|
||||
'./postfix-lib.pl');
|
||||
&ReadParse();
|
||||
if (substr($in{'file'}, 0, length($access{'apath'})) ne $access{'apath'}) {
|
||||
if (!&is_under_directory($access{'apath'}, $in{'file'})) {
|
||||
&error(&text('rfile_efile', $in{'file'}));
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ require (-r 'sendmail-lib.pl' ? './sendmail-lib.pl' :
|
||||
-r 'qmail-lib.pl' ? './qmail-lib.pl' :
|
||||
'./postfix-lib.pl');
|
||||
&ReadParseMime();
|
||||
if (substr($in{'file'}, 0, length($access{'apath'})) ne $access{'apath'}) {
|
||||
if (!&is_under_directory($access{'apath'}, $in{'file'})) {
|
||||
&error(&text('afile_efile', $in{'file'}));
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ require (-r 'sendmail-lib.pl' ? './sendmail-lib.pl' :
|
||||
'./postfix-lib.pl');
|
||||
&ReadParseMime();
|
||||
&error_setup($text{'ffile_err'});
|
||||
if (substr($in{'file'}, 0, length($access{'apath'})) ne $access{'apath'}) {
|
||||
if (!&is_under_directory($access{'apath'}, $in{'file'})) {
|
||||
&error(&text('ffile_efile', $in{'file'}));
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ require (-r 'sendmail-lib.pl' ? './sendmail-lib.pl' :
|
||||
-r 'qmail-lib.pl' ? './qmail-lib.pl' :
|
||||
'./postfix-lib.pl');
|
||||
&ReadParseMime();
|
||||
if (substr($in{'file'}, 0, length($access{'apath'})) ne $access{'apath'}) {
|
||||
if (!&is_under_directory($access{'apath'}, $in{'file'})) {
|
||||
&error(&text('rfile_efile', $in{'file'}));
|
||||
}
|
||||
$in{'replies_def'} || $in{'replies'} =~ /^\/\S+/ ||
|
||||
|
||||
@@ -2114,8 +2114,8 @@ if (!$main::ui_hidden_start_donejs++) {
|
||||
}
|
||||
|
||||
# Build list of tab titles and names
|
||||
my $tabnames = "[".join(",", map { "\""."e_escape($_->[0])."\"" } @$tabs)."]";
|
||||
my $tabtitles = "[".join(",", map { "\""."e_escape($_->[1])."\"" } @$tabs)."]";
|
||||
my $tabnames = &convert_to_json([map { $_->[0] } @$tabs]);
|
||||
my $tabtitles = &convert_to_json([map { $_->[1] } @$tabs]);
|
||||
$rv .= "<script type='text/javascript'>\n";
|
||||
$rv .= "document.${name}_tabnames = $tabnames;\n";
|
||||
$rv .= "document.${name}_tabtitles = $tabtitles;\n";
|
||||
|
||||
@@ -47,9 +47,9 @@ print &ui_table_row($text{'sendmail_login'},
|
||||
&ui_radio("login_def", $user ? 0 : 1,
|
||||
[ [ 1, $text{'sendmail_login1'}."<br>" ],
|
||||
[ 0, $text{'sendmail_login0'} ] ])." ".
|
||||
&ui_textbox("login_user", $user, 20)." ".
|
||||
$text{'sendmail_pass'}." ".
|
||||
&ui_textbox("login_pass", $pass, 20));
|
||||
&ui_textbox("login_user", $user, 12)." ".
|
||||
$text{'sendmail_pass'}." ".
|
||||
&ui_textbox("login_pass", $pass, 12));
|
||||
|
||||
# Authentication method
|
||||
$auth = $mconfig{'smtp_auth'};
|
||||
@@ -62,11 +62,12 @@ print &ui_table_row($text{'sendmail_auth'},
|
||||
$from = $mconfig{'webmin_from'};
|
||||
$fromdef = "webmin-noreply\@".&mailboxes::get_from_domain();
|
||||
print &ui_table_row($text{'sendmail_from'},
|
||||
&ui_opt_textbox("from", $from, 40,
|
||||
&text('sendmail_fromdef', $fromdef)."<br>",
|
||||
$text{'sendmail_fromaddr'})." ".
|
||||
$text{'sendmail_name'}." ".
|
||||
&ui_textbox("from_name", $mconfig{'webmin_from_name'}, 30), 3);
|
||||
&ui_radio_table("from_def", $from ? 0 : 1,
|
||||
[ [ 1, "", &text('sendmail_fromdef', $fromdef) ],
|
||||
[ 0, "", $text{'sendmail_fromaddr'}." ".
|
||||
&ui_textbox("from", $from, 40)."<br>\n".
|
||||
$text{'sendmail_name'}." ".
|
||||
&ui_textbox("from_name", $mconfig{'webmin_from_name'}, 30) ] ]), 3);
|
||||
|
||||
# Default to address for notifications
|
||||
$to = $gconfig{'webmin_email_to'};
|
||||
|
||||
@@ -93,6 +93,19 @@ sub list_secret_keys
|
||||
return grep { $_->{'secret'} } &list_keys();
|
||||
}
|
||||
|
||||
# key_matches_id(id, &key)
|
||||
# Returns 1 if some GnuPG key ID or fingerprint refers to the given key
|
||||
sub key_matches_id
|
||||
{
|
||||
my ($id, $key) = @_;
|
||||
$id = lc($id);
|
||||
return 1 if (lc($key->{'key'}) eq $id || lc($key->{'key'}) =~ /\Q$id\E$/);
|
||||
foreach my $key2 (@{$key->{'key2'}}) {
|
||||
return 1 if (lc($key2) eq $id || lc($key2) =~ /\Q$id\E$/);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
# key_fingerprint(&key)
|
||||
sub key_fingerprint
|
||||
{
|
||||
@@ -206,46 +219,66 @@ if ($key) {
|
||||
$pflag = "--batch --passphrase-file ".
|
||||
quotemeta(&get_passphrase_file($key));
|
||||
}
|
||||
my $cmd = "$gpgpath $pflag --output ".quotemeta($dstfile).
|
||||
" --decrypt ".quotemeta($srcfile);
|
||||
my ($fh, $fpid) = &proc::pty_process_exec($cmd);
|
||||
my ($error, $seen_pass, $keyid);
|
||||
$wait_for_debug = 1;
|
||||
my $retry = 0;
|
||||
while(1) {
|
||||
my $rv = &wait_for($fh, "passphrase:", "key,\\s+ID\\s+(\\S+),", "failed.*\\n", "error.*\\n", "invalid.*\\n", "signal caught.*\\n");
|
||||
if ($rv == 0) {
|
||||
# Only needed if caller didn't supply a key with passphrase
|
||||
last if ($seen_pass++);
|
||||
sleep(1);
|
||||
syswrite($fh, "$pass\n", length("$pass\n"));
|
||||
}
|
||||
elsif ($rv == 1) {
|
||||
# Only needed if caller didn't supply a key
|
||||
$keyid = $matches[1];
|
||||
my $rkey;
|
||||
($rkey) = grep { &indexof($matches[1], @{$_->{'key2'}}) >= 0 ||
|
||||
$_->{'key'} eq $matches[1] }
|
||||
&list_secret_keys();
|
||||
if ($rkey && $key) {
|
||||
# Does discovered key match?
|
||||
return &text('gnupg_ecryptkey2', "<tt>$keyid</tt>")
|
||||
if ($rkey->{'key'} ne $key->{'key'});
|
||||
unlink($dstfile);
|
||||
my $cmd = "$gpgpath --pinentry-mode loopback $pflag".
|
||||
" --output ".quotemeta($dstfile).
|
||||
" --decrypt ".quotemeta($srcfile);
|
||||
my ($fh, $fpid) = &proc::pty_process_exec($cmd);
|
||||
my $rerun = 0;
|
||||
$error = $seen_pass = $keyid = undef;
|
||||
while(1) {
|
||||
my $rv = &wait_for($fh, "passphrase:", "key,\\s+ID\\s+(\\S+),", "failed.*\\n", "error.*\\n", "invalid.*\\n", "signal caught.*\\n");
|
||||
if ($rv == 0) {
|
||||
# Only needed if caller didn't supply a key with passphrase
|
||||
last if ($seen_pass++);
|
||||
sleep(1);
|
||||
syswrite($fh, "$pass\n", length("$pass\n"));
|
||||
}
|
||||
elsif ($rkey && !$key) {
|
||||
# Discovered the key to use
|
||||
$pass = &get_passphrase($rkey);
|
||||
$key = $rkey;
|
||||
elsif ($rv == 1) {
|
||||
# Only needed if caller didn't supply a key
|
||||
$keyid = $matches[1];
|
||||
my $rkey;
|
||||
($rkey) = grep { &key_matches_id($matches[1], $_) }
|
||||
&list_secret_keys();
|
||||
if ($rkey && $key) {
|
||||
# Does discovered key match?
|
||||
return &text('gnupg_ecryptkey2', "<tt>$keyid</tt>")
|
||||
if (!&key_matches_id($keyid, $key));
|
||||
}
|
||||
elsif ($rkey && !$key) {
|
||||
# Discovered the key to use. Retry with a stored
|
||||
# passphrase file for newer GnuPG versions
|
||||
$pass = &get_passphrase($rkey);
|
||||
$key = $rkey;
|
||||
if (defined($pass) && !$retry++) {
|
||||
$pflag = "--batch --passphrase-file ".
|
||||
quotemeta(
|
||||
&get_passphrase_file(
|
||||
$key));
|
||||
$rerun++;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
elsif ($rv > 1) {
|
||||
$error++;
|
||||
last;
|
||||
}
|
||||
elsif ($rv < 0) {
|
||||
last;
|
||||
}
|
||||
}
|
||||
elsif ($rv > 1) {
|
||||
$error++;
|
||||
last;
|
||||
}
|
||||
elsif ($rv < 0) {
|
||||
last;
|
||||
if ($rerun) {
|
||||
kill('TERM', $fpid);
|
||||
close($fh);
|
||||
next;
|
||||
}
|
||||
close($fh);
|
||||
last;
|
||||
}
|
||||
close($fh);
|
||||
&reset_environment();
|
||||
unlink($srcfile);
|
||||
my $dst = &read_file_contents($dstfile);
|
||||
@@ -283,7 +316,8 @@ if (!defined($pass)) {
|
||||
return $text{'gnupg_esignpass'}.". ".
|
||||
&text('gnupg_canset', "/gnupg/edit_key.cgi?key=$key->{'key'}").".";
|
||||
}
|
||||
my $pflag = "--batch --passphrase-file ".quotemeta(&get_passphrase_file($key));
|
||||
my $pflag = "--batch --pinentry-mode loopback --passphrase-file ".
|
||||
quotemeta(&get_passphrase_file($key));
|
||||
my $cmd;
|
||||
if ($mode == 0) {
|
||||
$cmd = "$gpgpath $pflag --output ".quotemeta($dstfile)." --default-key $key->{'key'} --sign ".quotemeta($srcfile);
|
||||
|
||||
@@ -224,24 +224,57 @@ sub message_twofactor_totp
|
||||
my ($user) = @_;
|
||||
my $name = &get_display_hostname()." (".$user->{'name'}.")";
|
||||
my $str = "otpauth://totp/".$name."?secret=".$user->{'twofactor_id'};
|
||||
my $url;
|
||||
my $qrcode = &ui_tag('p',
|
||||
&text('twofactor_qrcode', "<tt>$user->{'twofactor_id'}</tt>"));
|
||||
if (&can_generate_qr()) {
|
||||
my $url;
|
||||
if (&get_product_name() eq 'usermin') {
|
||||
$url = "qr.cgi?size=6&str=".&urlize($str);
|
||||
$url = "qr.cgi?size=6";
|
||||
}
|
||||
else {
|
||||
$url = "$gconfig{'webprefix'}/webmin/qr.cgi?".
|
||||
"size=6&str=".&urlize($str);
|
||||
$url = "$gconfig{'webprefix'}/webmin/qr.cgi?size=6";
|
||||
}
|
||||
my $id = "twofactor_qr_".int(time())."_".int(rand(1000000));
|
||||
my $img = &ui_tag('img', undef,
|
||||
{ 'id' => $id, 'border' => 0,
|
||||
'style' => 'width:210px; height:210px; '.
|
||||
'border:1px solid #444;',
|
||||
'alt' => 'QR code' });
|
||||
my $id_js = "e_javascript($id);
|
||||
my $url_js = "e_javascript($url);
|
||||
my $str_js = "e_javascript($str);
|
||||
return <<EOF;
|
||||
$qrcode$img
|
||||
<script>
|
||||
(function() {
|
||||
const img = document.getElementById("$id_js"),
|
||||
body = "str=" + encodeURIComponent("$str_js");
|
||||
fetch("$url_js", {
|
||||
method: "POST",
|
||||
body: body
|
||||
}).then(function(response) {
|
||||
if (!response.ok) { return null; }
|
||||
return response.blob();
|
||||
}).then(function(blob) {
|
||||
if (!blob) { return; }
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = function() { img.src = reader.result; };
|
||||
reader.readAsDataURL(blob);
|
||||
}).catch(function() { });
|
||||
})();
|
||||
</script>
|
||||
<p>
|
||||
EOF
|
||||
}
|
||||
else {
|
||||
$url = "https://api.qrserver.com/v1/create-qr-code/?".
|
||||
"size=200x200&data=".&urlize($str);
|
||||
my $url = "https://api.qrserver.com/v1/create-qr-code/?".
|
||||
"size=200x200&data=".&urlize($str);
|
||||
my $img = &ui_tag('img', undef,
|
||||
{ 'src' => $url, 'border' => 0, 'alt' => 'QR code' });
|
||||
return <<EOF;
|
||||
$qrcode$img<p>
|
||||
EOF
|
||||
}
|
||||
my $rv;
|
||||
$rv .= &text('twofactor_qrcode', "<tt>$user->{'twofactor_id'}</tt>")."<p>\n";
|
||||
$rv .= "<img src='$url' border=0><p>\n";
|
||||
return $rv;
|
||||
}
|
||||
|
||||
# validate_twofactor_totp(id, token)
|
||||
|
||||
Reference in New Issue
Block a user