Compare commits

...

37 Commits

Author SHA1 Message Date
Ilia Ross
7507433bf1 Fix to drop dependency from Term::ANSIColor 2024-06-30 11:41:41 +03:00
Ilia Ross
248cb719c0 Fix restart command depend on config dir 2024-06-29 23:32:15 +03:00
Ilia Ross
94b7fdf0ec Fix to escape params 2024-06-29 23:29:55 +03:00
Ilia Ross
d89f6411b6 Fix to use has_command API 2024-06-29 20:34:29 +03:00
Ilia Ross
01d08a3605 Fix to make patch API work directly from URL 2024-06-28 19:48:51 +03:00
Ilia Ross
3d9497ff45 Fix command example 2024-06-28 01:51:02 +03:00
Ilia Ross
a6832450d1 Fix to restart Webmin when done 2024-06-28 01:38:28 +03:00
Ilia Ross
e41037388c Apply a patch to Webmin core or its modules from GitHub or a local file 2024-06-28 01:35:10 +03:00
Ilia Ross
a7b3af534b Fix to drop Threads module from recommended packages 2024-06-26 18:12:47 +03:00
Ilia Ross
87e006ceeb Add new API to verify session id 2024-06-26 14:39:23 +03:00
Ilia Ross
fbee8f0588 Add logging for active FTP conns 2024-06-20 20:04:19 +03:00
Jamie Cameron
e077b4da94 Start of work on support for requesting only a subset of hostnames 2024-06-19 22:05:22 -07:00
Jamie Cameron
38efad8265 Escape some inputs 2024-06-19 20:37:25 -07:00
Jamie Cameron
4d2a1fa084 Add spam folder name 2024-06-19 17:19:51 -07:00
Jamie Cameron
13b2eca3b1 Follow webmin code standards 2024-06-19 16:48:41 -07:00
Ilia Ross
aa4c3b1de6 Fix embedded styles margin 2024-06-19 16:55:25 +03:00
Ilia Ross
edaab4fd6f Fix embedded styles 2024-06-19 16:52:04 +03:00
Jamie Cameron
6fded0862c Merge pull request #2201 from webmin/dev/fix-last-command-match-freebsd
Fix listing last logins in FreeBSD
2024-06-18 16:16:37 -07:00
Ilia Ross
4006b0454e Fix to escape and not to truncate username 2024-06-18 23:51:19 +03:00
Ilia Ross
5d4ab58baa Fix the regex for last command on FreeBSD 2024-06-18 23:50:12 +03:00
Jamie Cameron
e6c7a60fe6 Merge pull request #2200 from webmin/dev/fix-last-command-regexes
Fix the regex to correctly match the output of the `last` command on contemporary systems
2024-06-18 13:38:21 -07:00
Ilia Ross
a75781d61a Fix the regex to correctly match the output of the last command on contemporary systems 2024-06-18 23:36:31 +03:00
Jamie Cameron
45348f5b02 Merge pull request #2199 from webmin/dev/last-dont-truncate-username
Fix not to truncate usernames
2024-06-18 11:33:01 -07:00
Ilia Ross
548c078813 Fix not to truncate usernames 2024-06-18 20:23:22 +03:00
Ilia Ross
76ccb2b7ed Fix support for Chrony in Debian systems 2024-06-17 15:09:42 +03:00
Ilia Ross
61d2081371 Add to support more passible options to systemd service file 2024-06-16 01:55:03 +03:00
Ilia Ross
458916b4d8 Fix to simplify make_date calls 2024-06-16 01:48:14 +03:00
Jamie Cameron
37451ad905 Merge pull request #2197 from vsc55/fix-2196
Fix metod detect Linux Gentoo.
2024-06-15 09:50:06 -07:00
Javier Pastor
9eec58a1f1 Fix metod detect Linux Gentoo. 2024-06-15 18:41:54 +02:00
Ilia Ross
178f527afa Fix to return an actual value 2024-06-15 16:50:04 +03:00
Jamie Cameron
cd1555b3e1 Merge branch 'master' of github.com:webmin/webmin 2024-06-12 16:53:02 -07:00
Jamie Cameron
2d7cfca67f Prevent duplicate also-notify and allow-transfer IPs 2024-06-12 16:52:50 -07:00
Jamie Cameron
e92a35b3ed Merge pull request #2195 from webmin/dev/xterm-acls-allow-not-enforce-sudo
Fix to allow disabling the enforcement of sudo-capable logins
2024-06-12 15:47:08 -07:00
Ilia Ross
e35efd0f00 Fix to always show the field 2024-06-13 01:36:41 +03:00
Ilia Ross
a12f385a5b Fix to consider user with the same name 2024-06-13 01:07:21 +03:00
Ilia Ross
5dc7cfafd7 Fix to allow disabling the enforcement of sudo-capable logins 2024-06-12 20:14:06 +03:00
Jamie Cameron
35aee74311 Merge pull request #2194 from webmin/dev/xterm-start-with-sudocapable
Fix to start with a sudo-capable user if possible
2024-06-11 18:19:05 -07:00
27 changed files with 404 additions and 82 deletions

File diff suppressed because one or more lines are too long

153
bin/patch Executable file
View File

@@ -0,0 +1,153 @@
#!/usr/bin/env perl
# patch - Apply a patch to Webmin core or its modules from GitHub or a local file
use strict;
use warnings;
use 5.010;
use Getopt::Long qw(:config permute pass_through);
use Pod::Usage;
use File::Basename;
use Cwd qw(cwd);
my %opt;
GetOptions(
'help|h' => \$opt{'help'},
'config|c=s' => \$opt{'config'},
);
pod2usage(0) if ($opt{'help'});
# Get Webmin path
my $path = cwd;
my $lib = "web-lib-funcs.pl";
if (!-r "$path/$lib") {
$path = dirname(dirname($0));
if (!-r "$path/$lib") {
$path = $path = Cwd::realpath('..');
}
}
# Init core
my $config_dir = $opt{'config'} || '/etc/webmin';
$ENV{'WEBMIN_CONFIG'} = $config_dir;
push(@INC, $path);
eval 'use WebminCore';
init_config();
# Check if curl is installed
if (!has_command('curl')) {
print "curl is not installed\n";
exit 1;
}
# Check if git is installed
if (!has_command('git')) {
print "git is not installed\n";
exit 1;
}
# Get patch URL or file
my $patch = $ARGV[0];
# Params check
if (!$patch) {
pod2usage(0);
exit 1;
}
# Patch check
if ($patch !~ /^https?:\/\//) {
if (!-r $patch) {
print "Patch file $patch doesn't exist\n";
exit 1;
}
}
elsif ($patch =~ /^https?:\/\/(github|gitlab)\.com/ &&
$patch !~ /\.patch$/ && $patch !~ /\.diff$/) {
$patch .= '.patch';
}
# Parse module name from URL
my $module = "";
if ($patch =~ m{https://(github|gitlab)\.com/[^/]+/([^/]+)/commit/[^/]+}) {
$module = $2;
$module = "" if ($2 eq 'webmin');
# Special handling for some modules
$module = $module =~ /^virtualmin-pro$/ ?
'virtual-server/pro' :
'virtual-server'
if $module =~ /^virtualmin-(gpl|pro)$/;
}
# Check if module exists
if (!-d "$path/$module") {
print "Module $module doesn't exist\n";
exit 1;
}
# Download command or cat patch file
my $cmd;
if ($patch =~ /^https?:\/\//) {
$cmd = "curl -s @{[quotemeta($patch)]}";
chdir "$path/$module";
}
else {
$cmd = "cat @{[quotemeta($patch)]}";
}
# Apply patch using Git
my $output = `$cmd 2>&1 | git apply --reject --verbose --whitespace=fix 2>&1`;
if ($output !~ /applied patch.*?cleanly/i) {
print "Patch failed: $output\n";
exit 1;
}
print "Patch applied successfully to:\n";
print " $1\n" while $output =~ /^Applied patch\s+(\S+)/mg;
system("$config_dir/restart");
=pod
=head1 NAME
patch
=head1 DESCRIPTION
Apply a patch to Webmin core or its modules from GitHub or a local file.
=head1 SYNOPSIS
webmin patch patch-url/file
=head1 OPTIONS
=over
=item --help, -h
Give this help list.
=item --config, -c
Specify the full path to the Webmin configuration directory. Defaults to
C</etc/webmin>
Examples of usage:
Apply a patch from a URL.
- webmin patch https://github.com/webmin/webmin/commit/e6a2bb15b0.patch
- webmin patch https://github.com/virtualmin/virtualmin-gpl/commit/f4433153d
Apply a patch from local file.
- cd /usr/libexec/webmin/virtual-server/pro &&
webmin patch /root/virtualmin-pro/patches/patch-1.patch
=back
=head1 LICENSE AND COPYRIGHT
Copyright 2024 Ilia Ross <ilia@virtualmin.com>

View File

@@ -1988,6 +1988,8 @@ if (@transfer) {
}
}
if (@notify) {
my %done;
@notify = grep { !$done{$_->{'name'}}++ } @notify;
my $also = { 'name' => 'also-notify',
'type' => 1,
'members' => \@notify};
@@ -1996,6 +1998,8 @@ if (@notify) {
'values' => [ 'yes' ] });
}
if (@transfer) {
my %done;
@transfer = grep { !$done{$_->{'name'}}++ } @transfer;
my $allow = { 'name' => 'allow-transfer',
'type' => 1,
'members' => \@transfer };

View File

@@ -21,7 +21,7 @@ if ($in{'source'} == 0) {
if (!$in{'local'})
{ &install_error($text{'download_elocal'}); }
if (!-r $in{'local'})
{ &install_error(&text('download_elocal2', $in{'local'})); }
{ &install_error(&text('download_elocal2', &html_escape($in{'local'}))); }
$source = $in{'local'};
@pfile = ( $in{'local'} );
$need_unlink = 0;
@@ -91,8 +91,9 @@ elsif ($in{'source'} == 3) {
$i = 0;
@fallback = ( );
foreach $yum (@cpanyum) {
print &text('download_yum', "<tt>$cpan[$i]</tt>",
"<tt>$yum->{'package'}</tt>"),"<br>\n";
print &text('download_yum',
"<tt>".&html_escape($cpan[$i])."</tt>",
"<tt>".&html_escape($yum->{'package'})."</tt>"),"<br>\n";
print "<ul>\n";
@got = &software::update_system_install(
$yum->{'package'});
@@ -154,7 +155,8 @@ elsif ($in{'source'} == 3) {
# Fail if any modules are missing from CPAN
for($i=0; $i<@cpan; $i++) {
push(@missing, "<tt>$cpan[$i]</tt>") if (!$source[$i]);
push(@missing, "<tt>".&html_escape($cpan[$i])."</tt>")
if (!$source[$i]);
}
if ($in{'missingok'}) {
@@ -167,11 +169,12 @@ elsif ($in{'source'} == 3) {
}
}
@cpan || &install_error(&text('download_ecpan',
join(" ", @missing)));
&html_escape(join(" ", @missing))));
}
elsif (@missing) {
# Fail due to missing modules
&install_error(&text('download_ecpan', join(" ", @missing)));
&install_error(&text('download_ecpan',
&html_escape(join(" ", @missing))));
}
$source = join("<br>", @source);
@@ -192,14 +195,16 @@ elsif ($in{'source'} == 3) {
&ftp_download($host, $file, $pfile, \$error,
\&progress_callback);
}
else { &install_error(&text('download_eurl', $m)); }
else {
&install_error(&text('download_eurl',&html_escape($m)));
}
&install_error($error) if ($error);
push(@pfile, $pfile);
}
$need_unlink = 1;
}
else {
&error("Unknown source mode $in{'source'}");
&error("Unknown source mode ".&html_escape($in{'source'}));
}
# Check if the file looks like a perl module
@@ -287,7 +292,7 @@ foreach $d (@dirs) {
close(MAKEFILE);
push(@allreqs, @prereqs);
}
system("rm -rf $mtemp");
&unlink_file($mtemp);
# Work out which pre-requesites are missing
@allreqs = &unique(@allreqs);

View File

@@ -350,7 +350,7 @@ if ($fh6) {
while(1) {
$$port++;
if ($$port < 0 || $$port > 65535) {
return "Failed to allocate a free port number: $port";
return "Failed to allocate a free port number: $$port";
}
$pack = pack_sockaddr_in($$port, INADDR_ANY);
next if (!bind($fh, $pack));

View File

@@ -1,7 +1 @@
body blockquote:not([style*="border-left"]) {
border-left: 1px solid #ccc;
margin-left: 6px;
margin-top: 0;
margin-bottom: 0;
padding-left: 12px;
}
body blockquote:not([style*="border-left"]){border-left:1px solid #ccc;margin-left:6px;margin-top:0;margin-bottom:0;padding-left:12px;}pre{white-space:break-spaces;margin:0 0 4px 0}

View File

@@ -2371,6 +2371,8 @@ if (ref($opts)) {
&print_tempfile(CFILE, "ExecReload=$kill -HUP \$MAINPID\n") if ($opts->{'reload'} eq '0');
&print_tempfile(CFILE, "ExecStop=$opts->{'stop'}\n") if ($opts->{'stop'});
&print_tempfile(CFILE, "ExecReload=$opts->{'reload'}\n") if ($opts->{'reload'});
&print_tempfile(CFILE, "ExecStartPre=$opts->{'startpre'}\n") if ($opts->{'startpre'});
&print_tempfile(CFILE, "ExecStartPost=$opts->{'startpost'}\n") if ($opts->{'startpost'});
&print_tempfile(CFILE, "Type=$opts->{'type'}\n") if ($opts->{'type'});
&print_tempfile(CFILE, "Environment=\"$opts->{'env'}\"\n") if ($opts->{'env'});
&print_tempfile(CFILE, "User=$opts->{'user'}\n") if ($opts->{'user'});
@@ -2381,8 +2383,8 @@ if (ref($opts)) {
&print_tempfile(CFILE, "RestartSec=$opts->{'restartsec'}\n") if ($opts->{'restartsec'});
&print_tempfile(CFILE, "TimeoutSec=$opts->{'timeout'}\n") if ($opts->{'timeout'});
&print_tempfile(CFILE, "TimeoutStopSec=$opts->{'timeoutstopsec'}\n") if ($opts->{'timeoutstopsec'});
&print_tempfile(CFILE, "StandardOutput=file:$opts->{'logstd'}\n") if ($opts->{'logstd'});
&print_tempfile(CFILE, "StandardError=file:$opts->{'logerr'}\n") if ($opts->{'logerr'});
&print_tempfile(CFILE, "StandardOutput=".($opts->{'logstd'} =~ /^\// ? 'file:' : '')."$opts->{'logstd'}\n") if ($opts->{'logstd'});
&print_tempfile(CFILE, "StandardError=".($opts->{'logerr'} =~ /^\// ? 'file:' : '')."$opts->{'logerr'}\n") if ($opts->{'logerr'});
}
&print_tempfile(CFILE, "\n");

View File

@@ -4412,10 +4412,10 @@ elsif ($line =~ /^END:VEVENT/) {
# Try to add local 'when (period)'
my $dtstart_local_obj =
$event{'_obj_dtstart_local_time'} =
make_date($event{'dtstart_local_timestamp'}, { _ });
make_date($event{'dtstart_local_timestamp'}, { });
my $dtend_local_obj =
$event{'_obj_dtend_local_time'} =
make_date($event{'dtend_local_timestamp'}, { _ });
make_date($event{'dtend_local_timestamp'}, { });
# Build when local, e.g.:
# Tue Jun 04, 2024 04:30 PM 05:15
# PM (Asia/Nicosia +0300)

View File

@@ -332,6 +332,7 @@ folder_inbox=Inbox
folder_sent=Sent
folder_drafts=Drafts
folder_trash=Trash
folder_spam=Spam
detach_err=Failed to detach file
detach_edir=No file or directory to save to entered

View File

@@ -107,7 +107,7 @@ if ($product eq "webmin") {
$size = int(`du -sk $tmp_dir`);
@deps = ( "perl", "libnet-ssleay-perl", "openssl", "libauthen-pam-perl", "libpam-runtime", "libio-pty-perl", "unzip", "shared-mime-info", "tar", "libdigest-sha-perl", "libdigest-md5-perl", "gzip" );
$deps = join(", ", @deps);
@recommends = ( "libdatetime-perl", "libdatetime-timezone-perl", "libdatetime-locale-perl", "libtime-piece-perl", "libencode-detect-perl", "libtime-hires-perl", "libsocket6-perl", "libthreads-perl", "libthreads-shared-perl", "lynx", "qrencode" );
@recommends = ( "libdatetime-perl", "libdatetime-timezone-perl", "libdatetime-locale-perl", "libtime-piece-perl", "libencode-detect-perl", "libtime-hires-perl", "libsocket6-perl", "lynx", "qrencode" );
$recommends = join(", ", @recommends);
open(CONTROL, ">$control_file");
print CONTROL <<EOF;

View File

@@ -87,7 +87,7 @@ Release: $rel
Provides: %{name}-%{version} perl(WebminCore)
Requires(pre): /usr/bin/perl
Requires: /bin/sh /usr/bin/perl perl(lib) perl(open) perl(Net::SSLeay) perl(Time::Local) perl(Data::Dumper) perl(File::Path) perl(File::Basename) perl(Digest::SHA) perl(Digest::MD5) openssl unzip tar gzip
Recommends: perl(DateTime) perl(DateTime::TimeZone) perl(DateTime::Locale) perl(Time::Piece) perl(Encode::Detect) perl(Time::HiRes) perl(Socket6) perl(threads) perl(threads::shared) lynx shared-mime-info perl-File-Basename perl-File-Path qrencode
Recommends: perl(DateTime) perl(DateTime::TimeZone) perl(DateTime::Locale) perl(Time::Piece) perl(Encode::Detect) perl(Time::HiRes) perl(Socket6) lynx shared-mime-info perl-File-Basename perl-File-Path qrencode
AutoReq: 0
License: BSD-3-clause
Group: System/Tools

View File

@@ -330,7 +330,7 @@ Corvus Latinux 8.0 redhat-linux 7.1 `cat /etc/latinux-release 2>/dev/null` =~
Immunix Linux $1 redhat-linux $1 $etc_issue =~ /Immunix.*\s([0-9\.]+)/i || `cat /etc/immunix-release 2>/dev/null` =~ /([0-9\.]+)/
# All versions of Gentoo (which don't appear to have version numbers)
Gentoo Linux Any version gentoo-linux * -d "/usr/portage"
Gentoo Linux Any version gentoo-linux * -d "/usr/portage" || `cat /etc/os-release 2>/dev/null` =~ /gentoo/
# Secure Linux (now called Trustix?)
Secure Linux 1.0 redhat-linux 7.2 `cat /etc/securelinux-release 2>/dev/null` =~ /SecureLinux.*1\.0/i

View File

@@ -56,7 +56,7 @@ if ($in{'action'} eq $text{'action_sync'}) {
if (defined($in{'sync_service_name'}) &&
defined($in{'sync_service_status'})) {
my $service_name = $in{'sync_service_name'};
if ($service_name !~ /^(chronyd|systemd-timesyncd)$/) {
if ($service_name !~ /^(chronyd|chrony|systemd-timesyncd)$/) {
&error(&text('error_serviceunknown', &html_escape($service_name)));
}
my $service_status = int($in{'sync_service_status'});

View File

@@ -5,7 +5,8 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './time-lib.pl';
our (%in, %text, %config, %access, $base_remote_user, $get_hardware_time_error);
our (%in, %text, %config, %access, $base_remote_user,
$get_hardware_time_error, $cronyd_name);
my ($rawdate, $rawhwdate, %system_date, $rawtime, %hw_date, $txt);
$txt = "";
@@ -174,13 +175,14 @@ if ( ( !$access{ 'sysdate' } && &has_command( "date" ) || !$access{ 'hwdate' } &
$config{'timeserver_hardware'}));
}
}
if (&foreign_require('init') && &init::action_status('chronyd') > 0 && &has_command("chronyc")) {
my $chronyd_running = &init::status_action('chronyd');
my $chronyd_run_atboot = &init::action_status('chronyd');
my $ui_hiddens = &ui_hidden("sync_service_name", "chronyd");
if (&foreign_require('init') &&
&init::action_status($cronyd_name) > 0 && &has_command("chronyc")) {
my $chronyd_running = &init::status_action($cronyd_name);
my $chronyd_run_atboot = &init::action_status($cronyd_name);
my $ui_hiddens = &ui_hidden("sync_service_name", $cronyd_name);
$ui_hiddens .= &ui_hidden("timeserver", $config{'timeserver'})
if (!$ntp_support);
print &ui_table_row(&text('index_tabsync2', 'chronyd'),
print &ui_table_row(&text('index_tabsync2', $cronyd_name),
$ui_hiddens.
&ui_radio("sync_service_status",
(($chronyd_run_atboot == 2 && $chronyd_running) ? 2 :

View File

@@ -13,6 +13,7 @@ our ($timezones_file, $currentzone_link, $currentzone_file, $timezones_dir,
$sysclock_file);
our ($get_hardware_time_error);
our $cron_cmd = "$module_config_directory/sync.pl";
our $cronyd_name = $gconfig{'os_type'} eq 'debian-linux' ? 'chrony' : 'chronyd';
our $rawtime;
if ($config{'zone_style'}) {
do "$config{'zone_style'}-lib.pl";
@@ -49,13 +50,13 @@ if (&has_command("ntpdate")) {
elsif (&has_command("sntp")) {
$out = &backquote_logged("sntp -s $servs 2>&1");
}
elsif (&foreign_require('init') && &init::action_status('chronyd') > 0 && &has_command("chronyc")) {
my $chronyd_running = &init::status_action('chronyd');
$out = &backquote_logged("systemctl restart chronyd 2>&1");
elsif (&foreign_require('init') && &init::action_status($cronyd_name) > 0 && &has_command("chronyc")) {
my $chronyd_running = &init::status_action($cronyd_name);
$out = &backquote_logged("systemctl restart $cronyd_name 2>&1");
$out .= &backquote_logged("chronyc makestep 2>&1");
sleep ($chronyd_running ? 5 : 15);
if (!$chronyd_running) {
&backquote_logged("systemctl stop chronyd 2>&1");
&backquote_logged("systemctl stop $cronyd_name 2>&1");
}
}
elsif (&foreign_require('init') && &init::action_status('systemd-timesyncd') > 0) {

View File

@@ -21,7 +21,8 @@ return 0;
sub open_last_command
{
local ($fh, $user) = @_;
open($fh, "last $user |");
local $quser = quotemeta($user);
open($fh, "(last -w $quser || last $quser) |");
}
# read_last_line(handle)
@@ -34,7 +35,9 @@ while(1) {
chop($line = <$fh>);
if (!$line) { return (); }
if ($line =~ /^(reboot|shutdown)/) { next; }
if ($line =~ /^(\S+)\s+(\S+)\s+(\S+)?\s+(\S+\s+\S+\s+\d+\s+\d+:\d+)\s+\-\s+(\S+)\s+\((\d+:\d+)\)/) {
if ($line =~ /^(\S+)\s+(\S+)\s+(\S+)?\s+(\S+\s+\S+\s+\d+\s+\d+:\d+)\s+\-\s+(\S+)\s+\((.*?\d+:\d+.*?)\)/) {
# root pts/0 10.211.55.2 Tue Nov 22 21:06 - 23:16 (02:10:00)
# root pts/1 10.211.55.2 Wed Jun 29 13:13 - shutdown (7+00:01:20)
return ($1, $2, $3, $4, $5 eq "shutdown" ? "Shutdown" :
$5 eq "crash" ? "Crash" : $5, $6);
}

View File

@@ -22,7 +22,7 @@ sub open_last_command
{
local ($fh, $user) = @_;
local $quser = quotemeta($user);
open($fh, "(last -F $quser || last $quser) |");
open($fh, "(last -F -w $quser || last -w $quser) |");
}
# read_last_line(handle)
@@ -39,12 +39,16 @@ while(1) {
# jcameron pts/0 fudu Thu Feb 22 09:47 - 10:15
return ($1, $2, $3, $4, $5 eq "down" ? "Shutdown" : $5, $6);
}
elsif ($line =~ /^(\S+)\s+(\S+)\s+(\S+)?\s+(\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+)\s+\-\s+(\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+)\s+\((\d+:\d+)\)/) {
elsif ($line =~ /^(\S+)\s+(\S+)\s+(\S+)?\s+(\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+)\s+\-\s+(\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+)\s+\((.*?\d+:\d+)\)/) {
# jcameron pts/0 fudu Thu Feb 22 09:47 - 10:15
# jcameron pts/0 fudu Sun Feb 4 02:26:28 2024 - Wed Feb 7 18:25:09 2024 (3+15:58)
return ($1, $2, $3, $4, $5 eq "down" ? "Shutdown" : $5, $6);
}
elsif ($line =~ /^(\S+)\s+(\S+)\s+(\S+)?\s+(\S+\s+\S+\s+\d+\s+\d+:\d+)\s+still/) {
# root pts/0 fudu Fri Feb 23 18:46 still logged in
elsif ($line =~ /^(\S+)\s+(\S+)\s+(\S+)?\s+(\S+\s+\S+\s+\d+\s+\d+:\d+)\s+(still|gone)/ ||
$line =~ /^(\S+)\s+(\S+)\s+(\S+)?\s+(\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+)\s+(still|gone)/) {
# root pts/0 fudu Fri Feb 23 18:46 still logged in
# root ftpd10 fudu Thu Jun 20 11:19 gone - no logout
# root pts/0 fudu Tue Jun 18 23:10:30 2024 still logged in
return ($1, $2, $3, $4);
}
}

View File

@@ -2098,10 +2098,10 @@ formatted like dd/mmm/yyyy hh:mm:ss. Parameters are :
=item seconds - Unix time is seconds to convert.
=item date-only-or-opts - If set to 1, exclude the time from the returned string.
=item date-only-or-opts - If set to 1 exclude the time from the returned string.
In case this param is a hash reference use it for options in a new DateTime::Locale
code or preserve the original, old logic
In case this param is a hash reference use it for options in a new
DateTime::Locale code or preserve the original, old logic
=item fmt - Optional, one of dd/mon/yyyy, dd/mm/yyyy, mm/dd/yyyy or yyyy/mm/dd
@@ -2116,27 +2116,31 @@ if (!$@ && $] > 5.011) {
my $opts = ref($only) ? $only : {};
my $locale_default = &get_default_system_locale();
my $locale_auto = &parse_accepted_language();
my $locale_name = $opts->{'locale'} || $gconfig{'locale_'.$base_remote_user} ||
my $locale_name = $opts->{'locale'} ||
$gconfig{'locale_'.$base_remote_user} ||
$gconfig{'locale_'.$remote_user} || $locale_auto ||
$gconfig{'locale'} || &get_default_system_locale();
my $tz = $opts->{'tz'};
if (!$tz) {
eval {
$tz =
DateTime::TimeZone->new(name => strftime("%z", localtime()))->name(); # +0200
$tz = DateTime::TimeZone->new(
name => strftime("%z", localtime()))->name(); # +0200
};
if ($@) {
eval {
$tz = DateTime::TimeZone->new(name => 'local')->name(); # Asia/Nicosia
$tz = DateTime::TimeZone->new(
name => 'local')->name(); # Asia/Nicosia
};
if ($@) {
$tz = DateTime::TimeZone->new(name => 'UTC')->name(); # UTC
$tz = DateTime::TimeZone->new(
name => 'UTC')->name(); # UTC
}
}
}
# Pre-process time locale
my $locale_military_status = sub {
return ($locale_military_name && $locale_military_name =~ /[a-z]/i) ? 2 :
return ($locale_military_name &&
$locale_military_name =~ /[a-z]/i) ? 2 :
($locale_military_name == 1) ? 1 : 0;
};
# Allow locales with military time (in 24h format)
@@ -2152,15 +2156,18 @@ if (!$@ && $] > 5.011) {
my $locale = DateTime::Locale->load($locale_name_loaded);
# Create a new locale out of base locale
if (&$locale_military_status() == 1) {
my %locale_data = $locale->locale_data;
$locale_data{'code'} = $locale_name_initial;
my %locale_data = $locale->locale_data;
$locale_data{'code'} = $locale_name_initial;
# Force 24h time
$locale_data{'glibc_date_1_format'} = '%a %b %e %H:%M:%S %Z %Y';
$locale_data{'glibc_date_1_format'} = '%a %b %e %H:%M:%S %Z %Y';
$locale_data{'glibc_datetime_format'} = '%a %d %b %Y %T %Z';
$locale_data{'glibc_time_format'} = '%T';
$locale_data{'glibc_time_format'} = '%T';
DateTime::Locale->register_from_data(%locale_data);
# Load newly cloned locale in 24h time format
$locale_military_name = $locale_name_loaded = $locale_name_initial;
$locale_military_name = $locale_name_loaded =
$locale_name_initial;
$locale = DateTime::Locale->load($locale_name_loaded);
}
my $locale_format_full_tz = $locale->glibc_date_1_format; # Sat 20 Nov 2286 17:46:39 UTC
@@ -2176,7 +2183,7 @@ if (!$@ && $] > 5.011) {
}
# Return fully detailed object
if (%{$opts}) {
if (ref($only)) {
# Can we get ago time
my $ago;
my $ago_secs = time() - $secs;
@@ -2194,18 +2201,31 @@ if (!$@ && $] > 5.011) {
"pretty" => $ago_obj->pretty
};
}
# my $xxxx = $locale->full_date_format;
my $data = {
# Wed Feb 8 05:09:39 PM UTC 2023
'full-tz-utc' => DateTime->from_epoch(locale => $locale_name_loaded, epoch => $secs)->strftime($locale_format_full_tz),
'full-tz-utc' => DateTime->from_epoch(
locale => $locale_name_loaded,
epoch => $secs)->strftime($locale_format_full_tz),
# Wed Feb 8 07:10:01 PM EET 2023
'full-tz' => DateTime->from_epoch(locale => $locale_name_loaded, epoch => $secs, time_zone => $tz)->strftime($locale_format_full_tz),
'full-tz' => DateTime->from_epoch(
locale => $locale_name_loaded,
epoch => $secs,
time_zone => $tz)->strftime($locale_format_full_tz),
# Wed 08 Feb 2023 07:11:26 PM EET
'full' => DateTime->from_epoch(locale => $locale_name_loaded, epoch => $secs, time_zone => $tz)->strftime($locale_format_full),
'full' => DateTime->from_epoch(
locale => $locale_name_loaded,
epoch => $secs,
time_zone => $tz)->strftime($locale_format_full),
# 02/08/2023
'short' => DateTime->from_epoch(locale => $locale_name_loaded, epoch => $secs, time_zone => $tz)->strftime($locale_format_short),
'short' => DateTime->from_epoch(
locale => $locale_name_loaded,
epoch => $secs,
time_zone => $tz)->strftime($locale_format_short),
# 07:12:07 PM
'time' => DateTime->from_epoch(locale => $locale_name_loaded, epoch => $secs, time_zone => $tz)->strftime($locale_format_time),
'time' => DateTime->from_epoch(
locale => $locale_name_loaded,
epoch => $secs,
time_zone => $tz)->strftime($locale_format_time),
'ago' => $ago,
'tz' => $tz,
'delimiter' => $locale_format_delimiter,
@@ -2216,10 +2236,16 @@ if (!$@ && $] > 5.011) {
$data->{'timeshort'} = $data->{'time'};
$data->{'timeshort'} =~ s/(\d+):(\d+):(\d+)(.*?)/$1:$2$4/;
# %c alternative with full week and month and no seconds in time (complete)
# Wednesday, February 8, 2023, 8:18 PM or 星期三, 2023年2月8日 20:18 or miércoles, 8 febrero 2023, 20:28
$data->{'monthfull'} = DateTime->from_epoch(locale => $locale_name_loaded, epoch => $secs, time_zone => $tz)->strftime("%B");
foreach (split(/\s+/, DateTime->from_epoch(locale => $locale_name_loaded, epoch => $secs, time_zone => $tz)->strftime("%A, %c"))) {
# %c alternative with full week and month and no seconds
# in time (complete)
# Wednesday, February 8, 2023, 8:18 PM or 星期三,
# 2023年2月8日 20:18 or miércoles, 8 febrero 2023, 20:28
$data->{'monthfull'} = DateTime->from_epoch(
locale => $locale_name_loaded, epoch => $secs,
time_zone => $tz)->strftime("%B");
foreach (split(/\s+/, DateTime->from_epoch(
locale => $locale_name_loaded, epoch => $secs,
time_zone => $tz)->strftime("%A, %c"))) {
if ($data->{'monthfull'} =~ /^$_/) {
$data->{'complete'} .= "$data->{'monthfull'} "
}
@@ -2227,13 +2253,24 @@ if (!$@ && $] > 5.011) {
$data->{'complete'} .= "$_ "
}
};
$data->{'year'} = DateTime->from_epoch(locale => $locale_name_loaded, epoch => $secs, time_zone => $tz)->strftime("%Y");
$data->{'day'} = DateTime->from_epoch(locale => $locale_name_loaded, epoch => $secs, time_zone => $tz)->strftime("%d");
$data->{'week'} = DateTime->from_epoch(locale => $locale_name_loaded, epoch => $secs, time_zone => $tz)->strftime("%a");
$data->{'weekfull'} = DateTime->from_epoch(locale => $locale_name_loaded, epoch => $secs, time_zone => $tz)->strftime("%A");
$data->{'month'} = DateTime->from_epoch(locale => $locale_name_loaded, epoch => $secs, time_zone => $tz)->strftime("%b");
$data->{'year'} = DateTime->from_epoch(
locale => $locale_name_loaded, epoch => $secs,
time_zone => $tz)->strftime("%Y");
$data->{'day'} = DateTime->from_epoch(
locale => $locale_name_loaded, epoch => $secs,
time_zone => $tz)->strftime("%d");
$data->{'week'} = DateTime->from_epoch(
locale => $locale_name_loaded, epoch => $secs,
time_zone => $tz)->strftime("%a");
$data->{'weekfull'} = DateTime->from_epoch(
locale => $locale_name_loaded, epoch => $secs,
time_zone => $tz)->strftime("%A");
$data->{'month'} = DateTime->from_epoch(
locale => $locale_name_loaded, epoch => $secs,
time_zone => $tz)->strftime("%b");
$data->{'complete'} =~ s/(\d+):(\d+):(\d+)(.*?)/$1:$2$4/;
($data->{'complete_short'} = $data->{'complete'}) =~ s/(.*?)([\s\,]*\Q$data->{'year'}\E.*)/$1/;
($data->{'complete_short'} = $data->{'complete'}) =~
s/(.*?)([\s\,]*\Q$data->{'year'}\E.*)/$1/;
if ($opts->{'get'}) {
return $data->{$opts->{'get'}};
@@ -7973,6 +8010,100 @@ return ref($s) && $s->{'host'} && $s->{'port'} ?
ref($s) ? "" : "$s.$$";
}
=head2 verify_session_id(session-id, [sessiondb], [miniserv])
Returns the username (or an array with user, last login, and IP address) for the
given session (or session hash) ID, or undefined if session ID is invalid.
Args:
$sid: The session ID to verify
$sessiondb: A reference to the session database hash (optional)
$miniserv: A reference to the miniserv configuration hash (optional)
Returns:
In list context: A list containing the user, last activity time, and IP
address associated with the session ID.
In scalar context: The user associated with the session ID.
If the session ID is not found, returns an empty list in list context
or undef in scalar context.
Usage:
Retrieve the username associated with the session ID or undef if
session is invalid.
my $user = verify_session_id($main::session_id);
Example of return:
root
Retrieve array containing the user, last login, and IP address
for given session hash ID or undef if session is invalid.
my (@user) = verify_session_id('BSRxr6wpF25lqeRinQ/sv0');
Example of return:
[
'root',
'1719401071',
'10.211.55.2'
]
Retrieve the username associated with the session ID, using given
session DB or undef if session is invalid.
my %sessiondb;
dbmopen(%sessiondb, "$var_directory/sessiondb", 0400);
my $user = verify_session_id($main::session_id, \%sessiondb);
dbmclose(%sessiondb);
Example of return:
someuser1
=cut
sub verify_session_id
{
my ($sid, $sessiondb, $miniserv) = @_;
my $hashsessionidfunc = \&miniserv::hash_session_id;
my %miniserv;
if ($miniserv) {
# Use provided miniserv configuration
%miniserv = %{$miniserv};
}
else {
# Load miniserv configuration if not provided
&get_miniserv_config(\%miniserv);
}
my %sessiondb_;
if ($sessiondb) {
# Use provided session database
%sessiondb_ = %{$sessiondb};
}
elsif (&foreign_available('acl')) {
# Use session database using ACL module API
&foreign_require("acl");
&acl::open_session_db(\%miniserv);
$hashsessionidfunc = \&acl::hash_session_id;
%sessiondb_ = %acl::sessiondb;
}
else {
return wantarray ? ( ) : undef;
}
# Verify given session (hash) ID against the session database
foreach my $k (grep { $sessiondb_{$_} } keys %sessiondb_) {
if ($k eq $sid ||
(defined($hashsessionidfunc) && $k eq $hashsessionidfunc->($sid))) {
my ($user, $last, $ip) = split(/\s+/, $sessiondb_{$k});
return wantarray ? ($user, $last, $ip) : $user;
}
}
# Return an empty list or undef if session ID is not found
return wantarray ? ( ) : undef;
}
=head2 remote_foreign_require(server, module, file)
Connects to rpc.cgi on a remote webmin server and have it open a session

View File

@@ -277,7 +277,9 @@ else {
my @doms = $config{'letsencrypt_doms'} ?
split(/\s+/, $config{'letsencrypt_doms'}) : ( $host );
print &ui_table_row($text{'ssl_letsdoms'},
&ui_textarea("dom", join("\n", @doms), 5, 40));
&ui_textarea("dom", join("\n", @doms), 5, 40)."<br>\n".
&ui_checkbox("subset", 1, $text{'ssl_subset'},
$config{'letsencrypt_subset'}));
# Apache vhost or other path
my @opts;

View File

@@ -424,6 +424,7 @@ ssl_letserr2=Alternately, check the <a href='$1'>module configuration</a> page t
ssl_letsdesc2=This page can be used to request a new certificate, which will overwrite any other currently have configured in Webmin. However, the Let's Encrypt service requires that your ownership of the certificate domain be validated by checking that this system hosts the website for the domain. This is done by placing a small temporary file in the website's document directory.
ssl_letsheader=Options for new SSL certificate
ssl_letsdoms=Hostnames for certificate
ssl_subset=Skip unverifiable hostnames?
ssl_letsmode=Let's Encrypt validation method
ssl_letsmode0=Apache virtual host matching hostname
ssl_letsmode1=Selected Apache virtual host

View File

@@ -58,14 +58,15 @@ return &software::missing_install_link(
# request_letsencrypt_cert(domain|&domains, webroot, [email], [keysize],
# [request-mode], [use-staging], [account-email],
# [reuse-key], [server-url, server-key, server-hmac])
# [reuse-key], [server-url, server-key, server-hmac],
# [allow-subset])
# Attempt to request a cert using a generated key with the Let's Encrypt client
# command, and write it to the given path. Returns a status flag, and either
# an error message or the paths to cert, key and chain files.
sub request_letsencrypt_cert
{
my ($dom, $webroot, $email, $size, $mode, $staging, $account_email,
$key_type, $reuse_key, $server, $server_key, $server_hmac) = @_;
$key_type, $reuse_key, $server, $server_key, $server_hmac, $subset) = @_;
my @doms = ref($dom) ? @$dom : ($dom);
$email ||= "root\@$doms[0]";
$mode ||= "web";
@@ -179,6 +180,7 @@ if ($letsencrypt_cmd) {
my $new_flags = "";
my $reuse_flags = "";
my $server_flags = "";
my $subset_flags = "";
$key_type ||= $config{'letsencrypt_algo'} || 'rsa';
if (&compare_version_numbers($cmd_ver, 1.11) < 0) {
$old_flags = " --manual-public-ip-logging-ok";
@@ -192,6 +194,9 @@ if ($letsencrypt_cmd) {
else {
$reuse_flags = " --no-reuse-key";
}
if ($subset) {
$subset_flags = " --allow-subset-of-names";
}
$reuse_flags = "" if ($reuse_key && $reuse_key == -1);
if ($server) {
$server_flags = " --server ".quotemeta($server);
@@ -227,6 +232,7 @@ if ($letsencrypt_cmd) {
$old_flags.
$server_flags.
$new_flags.
$subset_flags.
" 2>&1)");
&reset_environment();
}
@@ -245,6 +251,7 @@ if ($letsencrypt_cmd) {
$old_flags.
$server_flags.
$new_flags.
$subset_flags.
" 2>&1)");
&reset_environment();
}
@@ -260,6 +267,7 @@ if ($letsencrypt_cmd) {
$old_flags.
$server_flags.
$new_flags.
$subset_flags.
" 2>&1)");
&reset_environment();
}

View File

@@ -76,7 +76,7 @@ else {
if ($in{'save'}) {
# Just update renewal
&save_renewal_only(\@doms, $webroot, $mode);
&save_renewal_only(\@doms, $webroot, $mode, $size, $in{'subset'});
&redirect("edit_ssl.cgi");
}
else {
@@ -88,7 +88,9 @@ else {
'letsencrypt_doing',
"<tt>".&html_escape(join(", ", @doms))."</tt>",
"<tt>".&html_escape($webroot)."</tt>"),"<p>\n";
my ($ok, $cert, $key, $chain) = &request_letsencrypt_cert(\@doms, $webroot, undef, $size, $mode, $in{'staging'});
my ($ok, $cert, $key, $chain) = &request_letsencrypt_cert(
\@doms, $webroot, undef, $size, $mode, $in{'staging'},
undef, 0, undef, undef, undef, $in{'subset'});
if (!$ok) {
print &text('letsencrypt_failed', $cert),"<p>\n";
}
@@ -148,15 +150,16 @@ else {
&ui_print_footer("", $text{'index_return'});
}
# save_renewal_only(&doms, webroot, mode)
# save_renewal_only(&doms, webroot, mode, size, subset-mode)
# Save for future renewals
sub save_renewal_only
{
my ($doms, $webroot, $mode) = @_;
my ($doms, $webroot, $mode, $size, $subset) = @_;
$config{'letsencrypt_doms'} = join(" ", @$doms);
$config{'letsencrypt_webroot'} = $webroot;
$config{'letsencrypt_mode'} = $mode;
$config{'letsencrypt_size'} = $size;
$config{'letsencrypt_subset'} = $subset;
&save_module_config();
if (&foreign_check("webmincron")) {
my $job = &find_letsencrypt_cron_job();

View File

@@ -128,7 +128,7 @@ if (!$eol_data->{'_eol_timestamp'}) {
&error_stderr("The provided data is not a valid EOL data hash reference");
return undef;
}
my $eol_date = &make_date($eol_data->{'_eol_timestamp'}, { '_' => 1 });
my $eol_date = &make_date($eol_data->{'_eol_timestamp'}, { });
if (ref($eol_date)) {
my $eol_in = sub {
my $eol_date = shift;

View File

@@ -10,6 +10,10 @@ my ($o) = @_;
print &ui_table_row($text{'acl_user'},
&ui_opt_textbox("user", $o->{'user'} eq '*' ? undef : $o->{'user'},
20, $text{'acl_sameuser'}));
print &ui_table_row($text{'acl_sudoenforce'},
&ui_yesno_radio("sudoenforce",
$o->{'sudoenforce'} == 1 ? 1 : 0));
}
sub acl_security_save
@@ -17,4 +21,5 @@ sub acl_security_save
my ($o) = @_;
$o->{'user'} = $in{'user_def'} ? '*' : $in{'user'};
$o->{'sudoenforce'} = $in{'sudoenforce'} ? 1 : 0;
}

View File

@@ -1 +1,2 @@
user=root
sudoenforce=1

View File

@@ -175,7 +175,8 @@ my $user = $access{'user'};
if ($user eq "*") {
$user = $remote_user;
}
elsif ($user eq "root" && $remote_user ne $user && !$in{'user'}) {
elsif ($user eq "root" && $remote_user ne $user && !$in{'user'} &&
$access{'sudoenforce'} ne '0') {
# If possible, start with a sudo-capable user
my @uinfo = getpwnam($remote_user);
if (@uinfo && $uinfo[7]) {

View File

@@ -9,3 +9,4 @@ index_eproxy=The Terminal module cannot be used when accessing Webmin via anothe
acl_user=Run shell as Unix user
acl_sameuser=Same as Webmin login
acl_sudoenforce=Enforce <em>sudo</em>-only privileges