diff --git a/src/opnsense/mvc/app/library/OPNsense/Auth/Radius.php b/src/opnsense/mvc/app/library/OPNsense/Auth/Radius.php index 50add310d71..ac09e33c045 100644 --- a/src/opnsense/mvc/app/library/OPNsense/Auth/Radius.php +++ b/src/opnsense/mvc/app/library/OPNsense/Auth/Radius.php @@ -96,6 +96,11 @@ class Radius extends Base implements IAuthConnector */ private $lastAuthProperties = []; + /** + * @var string|null outcome of the last authenticate() call (accept|reject|timeout|error) + */ + private $lastResult = null; + /** * @var boolean when set, synchronize groups defined in memberOf attribute to local database */ @@ -174,6 +179,7 @@ public function setProperties($config) 'radius_secret' => 'sharedSecret', 'radius_stationid' => 'calledStationId', 'radius_timeout' => 'timeout', + 'radius_max_retries' => 'maxRetries', 'refid' => 'nasIdentifier', ]; @@ -245,6 +251,15 @@ public function getLastAuthProperties() return $this->lastAuthProperties; } + /** + * outcome of the last authenticate() call + * @return string|null one of accept, reject, timeout, error, or null + */ + public function getLastResult() + { + return $this->lastResult; + } + /** * send start accounting message to radius * @param string $username username @@ -512,6 +527,7 @@ public function preauth($config) public function authenticate($username, $password) { $this->lastAuthProperties = [];// reset auth properties + $this->lastResult = null; $radius = radius_auth_open(); $error = null; @@ -592,6 +608,7 @@ public function authenticate($username, $password) break; default: syslog(LOG_ERR, 'Unsupported protocol ' . $this->protocol); + $this->lastResult = 'error'; return false; } } @@ -601,10 +618,12 @@ public function authenticate($username, $password) // log errors and perform actual authentication request if ($error != null) { + $this->lastResult = 'error'; syslog(LOG_ERR, 'RadiusError: ' . $error); } else { $request = radius_send_request($radius); if (!$request) { + $this->lastResult = 'timeout'; syslog(LOG_ERR, 'RadiusError: ' . radius_strerror($radius)); } else { switch ($request) { @@ -652,13 +671,16 @@ public function authenticate($username, $password) $this->syncDefaultGroups ); } + $this->lastResult = 'accept'; return true; break; case RADIUS_ACCESS_REJECT: + $this->lastResult = 'reject'; return false; break; default: // unexpected result, log + $this->lastResult = 'error'; syslog(LOG_ERR, 'Radius unexpected response:' . $request); } } diff --git a/src/opnsense/scripts/openvpn/user_pass_verify.php b/src/opnsense/scripts/openvpn/user_pass_verify.php index 60e509707de..5c9ca926827 100755 --- a/src/opnsense/scripts/openvpn/user_pass_verify.php +++ b/src/opnsense/scripts/openvpn/user_pass_verify.php @@ -156,6 +156,18 @@ function do_auth($common_name, $serverid, $method, $auth_file) } return true; } + // only cascade on a decisive answer; an upstream that didn't + // respond may dedup the next request and suppress new MFA prompts + if ( + method_exists($authenticator, 'getLastResult') + && $authenticator->getLastResult() === 'timeout' + ) { + syslog( + LOG_WARNING, + "auth source '{$authName}' did not respond for user '{$username}', not cascading" + ); + break; + } } } return "user '{$username}' could not authenticate."; diff --git a/src/www/system_authservers.php b/src/www/system_authservers.php index 383a8dd7c89..e84798c742f 100644 --- a/src/www/system_authservers.php +++ b/src/www/system_authservers.php @@ -99,6 +99,7 @@ $pconfig['radius_acct_port'] = $a_server[$id]['radius_acct_port'] ?? ''; $pconfig['radius_secret'] = $a_server[$id]['radius_secret'] ?? ''; $pconfig['radius_timeout'] = $a_server[$id]['radius_timeout'] ?? ''; + $pconfig['radius_max_retries'] = $a_server[$id]['radius_max_retries'] ?? ''; $pconfig['radius_stationid'] = $a_server[$id]['radius_stationid'] ?? ''; if (!empty($pconfig['radius_auth_port']) && @@ -217,6 +218,9 @@ if (($pconfig['type'] == "radius") && isset($pconfig['radius_timeout']) && !empty($pconfig['radius_timeout']) && (!is_numeric($pconfig['radius_timeout']) || (is_numeric($pconfig['radius_timeout']) && ($pconfig['radius_timeout'] <= 0)))) { $input_errors[] = gettext("RADIUS Timeout value must be numeric and positive."); } + if (($pconfig['type'] == "radius") && isset($pconfig['radius_max_retries']) && !empty($pconfig['radius_max_retries']) && (!is_numeric($pconfig['radius_max_retries']) || (is_numeric($pconfig['radius_max_retries']) && ($pconfig['radius_max_retries'] <= 0)))) { + $input_errors[] = gettext("RADIUS Max Retries value must be numeric and positive."); + } if (empty($pconfig['name'])) { $input_errors[] = gettext('A server name must be provided.'); } elseif (strpos($pconfig['name'], ',') !== false) { @@ -278,6 +282,12 @@ $server['radius_timeout'] = 5; } + if (!empty($pconfig['radius_max_retries'])) { + $server['radius_max_retries'] = $pconfig['radius_max_retries']; + } else { + unset($server['radius_max_retries']); + } + if (!empty($pconfig['radius_stationid'])) { $server['radius_stationid'] = $pconfig['radius_stationid']; } else { @@ -368,6 +378,7 @@ 'radius_secret', 'radius_srvcs', 'radius_timeout', + 'radius_max_retries', 'radius_stationid', 'sync_create_local_users', 'sync_memberof', @@ -864,6 +875,17 @@ + + + + + + +