Compare commits

...

32 Commits

Author SHA1 Message Date
Ilia Ross
d2b4fa89c5 Fix to match short GPG key IDs to full fingerprints
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://forum.virtualmin.com/t/gpg-encryption-in-usermin/136729/32?u=ilia
2026-03-29 15:20:10 +02:00
Ilia Ross
3a3b202a96 Add safe explicit TLS fallback for FTP backups for fsdump module
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/pull/2646
2026-03-26 12:02:24 +02:00
Ilia Ross
30f08f73fb Fix to support newer GnuPG passphrase handling
* Note: Use loopback pinentry for decrypt operation and retry decryption with the discovered secret key's stored passphrase on newer GnuPG versions

https://forum.virtualmin.com/t/gpg-encryption-in-usermin/136729/26?u=ilia
2026-03-26 11:48:40 +02:00
Ilia Ross
e499b5b3a5 Fix to use loopback pinentry mode for GPG passphrase handling
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://forum.virtualmin.com/t/gpg-encryption-in-usermin/136729/19?u=ilia
2026-03-25 18:30:13 +02:00
Ilia Ross
443cf449eb Fix to use loopback pinentry for GPG decryption
https://forum.virtualmin.com/t/gpg-encryption-in-usermin/136729/19?u=ilia
2026-03-25 12:52:26 +02:00
Jamie Cameron
58d6308589 Properly check allowed directory paths
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-24 21:20:36 -07:00
Jamie Cameron
02ed8e8fbd New version bump
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-23 21:50:30 -07:00
Jamie Cameron
3a0dea1d2c Merge pull request #2648 from swelljoe/bind8-fix-warnings
Fix bind8 undefined warnings and sort/splice with non-numeric value
2026-03-23 20:27:08 -07:00
Joe Cooper
916d22b55b One more undefined 2026-03-23 22:22:31 -05:00
Joe Cooper
1f1a7e4562 Fix sort/splice bug 2026-03-23 21:54:18 -05:00
Joe Cooper
ac68a0be0c Fix bind8 undefined warnings 2026-03-23 21:36:29 -05:00
Ilia Ross
263cc142a6 Update changelog for 2.630
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-24 00:40:34 +02:00
Ilia Ross
19fdea395b Fix tab data escaping with proper JSON encoding
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
*Note: This is important if we want to support tags inside tab name, for example in case of showing a count of elements using HTML tag created with `ui_tag('tt')`
2026-03-21 21:00:10 +02:00
Ilia Ross
40f82d0df3 Revert "Fix to always avoid new lines inside the tag"
This reverts commit 39ab2c5f02.
2026-03-21 20:56:01 +02:00
Ilia Ross
39ab2c5f02 Fix to always avoid new lines inside the tag 2026-03-21 20:17:41 +02:00
Ilia Ross
f39a59bdce Fix to add module recommended packages
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-19 18:46:01 +02:00
Ilia Ross
a4846f5f32 Add an option to disable external programs from performing upgrades
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-17 20:11:36 +02:00
Ilia Ross
5558910722 Add API to activate and deactivate a service 2026-03-17 20:05:06 +02:00
Ilia Ross
bebd99d656 Add informational note about updates #2639
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-17 12:56:03 +02:00
Ilia Ross
bad0d2f821 Fix fields size
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
[no-build]
2026-03-16 11:22:13 +02:00
Jamie Cameron
4797852f6f Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-15 12:26:50 -07:00
Jamie Cameron
d467810076 Fix layout of from address field
https://github.com/webmin/webmin/issues/2644
2026-03-15 12:26:43 -07:00
Ilia Ross
6d1ec1a3e1 Fix missing tags
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-15 16:28:38 +02:00
Jamie Cameron
82ea895c81 No need for PHP prefix since all we're installing is PHP
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/2641
2026-03-14 17:43:06 -07:00
Jamie Cameron
36e699eb29 Merge branch 'master' of github.com:webmin/webmin 2026-03-14 17:37:14 -07:00
Jamie Cameron
04e8df863a Better button name
https://github.com/webmin/webmin/issues/2643
2026-03-14 17:36:59 -07:00
Ilia Ross
96c2312349 Fix to improve the name of the downloaded backup file #2570
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-14 13:19:05 +02:00
Jamie Cameron
1d594e82f0 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-11 22:24:06 -07:00
Jamie Cameron
cbd96a4176 Make code more readable 2026-03-11 16:43:46 -07:00
Ilia Ross
ed17ade510 Fix not to leak 2FA auth secret to logs
https://github.com/webmin/webmin/pull/2638

[no-build]
2026-03-12 01:17:42 +02:00
Ilia Ross
dc63aa22a5 Fix to build HTML nicely
[no-build]
2026-03-12 01:06:49 +02:00
Ilia Ross
1b9b9ae21f Fix to show test form for two-factor only when enrolling for yourself
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-10 23:31:47 +02:00
30 changed files with 307 additions and 98 deletions

View File

@@ -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

View File

@@ -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'}&nbsp;\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'}".
"&nbsp;&nbsp;".
&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'});

View File

@@ -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'} || "");

View File

@@ -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) {

View File

@@ -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'} ],

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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") {

View File

@@ -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");
}
}

View File

@@ -430,8 +430,8 @@ if (!$main::ui_hidden_start_donejs++) {
}
# Build list of tab titles and names
my $tabnames = "[".join(",", map { "\"".&quote_escape($_->[0])."\"" } @$tabs)."]";
my $tabtitles = "[".join(",", map { "\"".&quote_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;

View File

@@ -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

View File

@@ -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'} ] ]);

View File

@@ -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

View File

@@ -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

View File

@@ -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'});

View File

@@ -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

View File

@@ -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'});

View File

@@ -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

View 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('afile_efile', $in{'file'}));
}

View 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'}));
}

View 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'}));
}

View 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'}));
}

View 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'}));
}

View 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+/ ||

View File

@@ -2114,8 +2114,8 @@ if (!$main::ui_hidden_start_donejs++) {
}
# Build list of tab titles and names
my $tabnames = "[".join(",", map { "\"".&quote_escape($_->[0])."\"" } @$tabs)."]";
my $tabtitles = "[".join(",", map { "\"".&quote_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";

View File

@@ -1 +1 @@
2.621
2.630

View File

@@ -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)."&nbsp;".
$text{'sendmail_pass'}."&nbsp;&nbsp;".
&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'};

View File

@@ -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);

View File

@@ -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 = &quote_javascript($id);
my $url_js = &quote_javascript($url);
my $str_js = &quote_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)