Fix SSL and TCP monitor false uninstalled alerts

ⓘ Report SSL certificate and TCP connection, timeout, and DNS failures as down or timed out instead of not installed, and tighten built-in status
monitor loading.

The SSL certificate and TCP monitors returned -1 (not installed) for
connection failures, timeouts, and DNS errors, causing false "service
uninstalled" then "back up" notifications. Report these as down (0) or timed out (-3) instead, reserving -1 for genuinely missing.

Also survive a broken monitor library without aborting the whole scheduled check run, and fix the remote alive-monitor Webmin-down fallback typo.

https://forum.virtualmin.com/t/service-monitor-uninstalled-then-back-up/137514?u=ilia
This commit is contained in:
Ilia Ross
2026-07-02 16:16:46 +02:00
parent 1dc4d14ff8
commit eb40e65fb3
5 changed files with 35 additions and 9 deletions

View File

@@ -3,6 +3,7 @@
#### 2.652 (July, 2026)
* Fix to recognize hex numeric HTML entities to work in various elements
* Fix `patch` sub-command to reload Webmin instead of restarting
* Fix SSL certificate and TCP monitors to report connection, timeout and DNS failures as down or timed out rather than uninstalled
#### 2.651 (June 28, 2026)
* Fix Certbot-backed certificate requests and renewals to correctly parse PEM paths after issuance

View File

@@ -102,6 +102,7 @@ mon_down=Down
mon_webmin=Webmin Down
mon_timeout=Timed out
mon_not=Not installed
mon_eload=Monitor program $1 failed to load : $2
mon_ondown=If monitor goes down, run command
mon_onup=If monitor comes up, run command
mon_ontimeout=If monitor times out, run command
@@ -222,6 +223,8 @@ tcp_alarm=Connection timeout
tcp_ehost=Missing or invalid hostname
tcp_eport=Missing or invalid port number
tcp_ealarm=Missing or invalid connection timeout
tcp_esocket=Failed to create TCP socket : $1
tcp_eresolve=Failed to resolve host name $1
http_url=URL to request
http_alarm=Connection timeout
@@ -558,6 +561,7 @@ sslcert_eurl=Missing, invalid or non-SSL URL
sslcert_efile=Missing or invalid certificate file
sslcert_ecert=Could not get certificate
sslcert_edown=Web server is down
sslcert_etimeout=Timed out connecting to web server
sslcert_days=Days before expiry to fail
sslcert_when=When expired
sslcert_mismatch=Also detect hostname mismatch?

View File

@@ -2,6 +2,8 @@
sub get_sslcert_status
{
# The openssl command is needed for all checks
&has_command("openssl") || return { 'up' => -1 };
local $up = 0;
local $desc;
local $certfile;
@@ -21,12 +23,15 @@ if ($_[0]->{'url'}) {
# don't support it
$cmd = "openssl s_client -host ".quotemeta($host).
" -port ".quotemeta($port)." </dev/null 2>&1";
$out = &backquote_with_timeout($cmd, 10);
($out, $timed) = &backquote_with_timeout($cmd, 10);
}
if ($?) {
# Connection failed
return { 'up' => -1,
'desc' => $text{'sslcert_edown'} };
# Connection failed or timed out, meaning the web server
# is down rather than the monitor being uninstalled
return $timed ? { 'up' => -3,
'desc' => $text{'sslcert_etimeout'} }
: { 'up' => 0,
'desc' => $text{'sslcert_edown'} };
}
# Extract the cert part and save
@@ -62,6 +67,11 @@ if ($info =~ /Not\s*Before\s*:\s*(.*)/i) {
if ($info =~ /Not\s+After\s*:\s*(.*)/i) {
$end = &mailboxes::parse_mail_date("$1");
}
if (!$end) {
# Certificate could not be parsed, so it cannot be considered OK
return { 'up' => 0,
'desc' => $text{'sslcert_ecert'} };
}
local $now = time();
if ($start && $now < $start) {
# Too new?!

View File

@@ -168,7 +168,7 @@ foreach $r (&expand_remotes($serv)) {
$remote_error_msg = undef;
&remote_foreign_require($r, 'status', 'status-lib.pl')
if (!$done_remote_status{$r}++);
my $webmindown = $s->{'type'} eq 'alive' ? 0 : -2;
my $webmindown = $serv->{'type'} eq 'alive' ? 0 : -2;
if ($remote_error_msg) {
$rv = { 'up' => $webmindown,
'desc' => "$text{'mon_webmin'} : $remote_error_msg" };
@@ -195,7 +195,11 @@ foreach $r (&expand_remotes($serv)) {
}
else {
# Just include and use the local monitor library
do "${t}-monitor.pl" if (!$done_monitor{$t}++);
my $mfile = "$module_root_directory/${t}-monitor.pl";
if (-r $mfile && !$done_monitor{$t}++) {
do $mfile;
$done_monitor_err{$t} = "$@" if ($@);
}
my $func = "get_${t}_status";
if (defined(&$func)) {
$rv = &$func($serv,
@@ -203,7 +207,12 @@ foreach $r (&expand_remotes($serv)) {
$fromcgi);
}
else {
# Monitor type is unknown, or its program
# failed to load
$rv = { 'up' => -1 };
$rv->{'desc'} = &text('mon_eload', $mfile,
$done_monitor_err{$t})
if ($done_monitor_err{$t});
}
}
alarm(0);

View File

@@ -5,9 +5,11 @@ sub get_tcp_status
{
# Connect to the server
socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp")) ||
return { 'up' => -1 };
return { 'up' => -3,
'desc' => &text('tcp_esocket', $!) };
local $addr = inet_aton($_[0]->{'host'});
return { 'up' => -1 } if (!$addr);
return { 'up' => 0,
'desc' => &text('tcp_eresolve', $_[0]->{'host'}) } if (!$addr);
local $st = time();
local $rv;
eval {
@@ -18,7 +20,7 @@ eval {
alarm(0);
};
return { 'up' => 0 } if ($@);
return { 'up' => $rv,
return { 'up' => $rv ? 1 : 0,
'time' => time() - $st };
}