%PDF- %PDF-
| Direktori : /proc/self/root/backups/router/usr/local/opnsense/mvc/app/library/OPNsense/Auth/ |
| Current File : //proc/self/root/backups/router/usr/local/opnsense/mvc/app/library/OPNsense/Auth/Radius.php |
<?php
/*
* Copyright (C) 2015 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Auth;
/**
* Class Radius connector
* @package OPNsense\Auth
*/
class Radius extends Base implements IAuthConnector
{
/**
* @var null radius hostname / ip
*/
private $radiusHost = null;
/**
* @var null port to use for authentication
*/
private $authPort = "1812";
/**
* @var null port to use for accounting
*/
private $acctPort = null;
/**
* @var null shared secret to use for this server
*/
private $sharedSecret = null;
/**
* @var string radius protocol selection
*/
private $protocol = 'PAP';
/**
* @var int timeout to use
*/
private $timeout = 10;
/**
* @var int maximum number of retries
*/
private $maxRetries = 3;
/**
* @var null RADIUS_NAS_IDENTIFIER to use, read from config.
*/
private $nasIdentifier = 'local';
/**
* @var array internal list of authentication properties (returned by radius auth)
*/
private $lastAuthProperties = [];
/**
* @var boolean when set, synchronize groups defined in memberOf attribute to local database
*/
private $syncMemberOf = false;
/**
* @var boolean when set, allow local user creation
*/
private $syncCreateLocalUsers = false;
/**
* @var array limit the groups which will be considered for sync, empty means all
*/
private $syncMemberOfLimit = [];
/**
* @var array list of groups to add by default
*/
private $syncDefaultGroups = [];
/**
* type name in configuration
* @return string
*/
public static function getType()
{
return 'radius';
}
/**
* user friendly description of this authenticator
* @return string
*/
public function getDescription()
{
return gettext("Radius");
}
/**
* set connector properties
* @param array $config connection properties
*/
public function setProperties($config)
{
// map properties to object
$confMap = array('host' => 'radiusHost',
'radius_secret' => 'sharedSecret',
'radius_timeout' => 'timeout',
'radius_auth_port' => 'authPort',
'radius_acct_port' => 'acctPort',
'radius_protocol' => 'protocol',
'refid' => 'nasIdentifier'
);
// map properties 1-on-1
foreach ($confMap as $confSetting => $objectProperty) {
if (!empty($config[$confSetting]) && property_exists($this, $objectProperty)) {
$this->$objectProperty = $config[$confSetting];
}
}
if (!empty($config['sync_create_local_users'])) {
$this->syncCreateLocalUsers = true;
}
if (!empty($config['sync_memberof'])) {
$this->syncMemberOf = true;
}
if (!empty($config['sync_memberof_groups'])) {
$this->syncMemberOfLimit = explode(",", strtolower($config['sync_memberof_groups']));
}
if (!empty($config['sync_default_groups'])) {
$this->syncDefaultGroups = explode(",", strtolower($config['sync_default_groups']));
}
}
/**
* retrieve configuration options
* @return array
*/
public function getConfigurationOptions()
{
$options = [];
$options['radius_protocol'] = [];
$options['radius_protocol']['name'] = gettext('Protocol');
$options['radius_protocol']['type'] = 'dropdown';
$options['radius_protocol']['default'] = 'PAP';
$options['radius_protocol']['options'] = [
'PAP' => 'PAP',
'MSCHAPv2' => 'MSCHAPv2'
];
$options['radius_protocol']['validate'] = function ($value) {
if (!in_array($value, ['PAP', 'MSCHAPv2'])) {
return [gettext('Invalid protocol specified')];
} else {
return [];
}
};
return $options;
}
/**
* return session info
* @return array mixed named list of authentication properties
*/
public function getLastAuthProperties()
{
return $this->lastAuthProperties;
}
/**
* send start accounting message to radius
* @param string $username username
* @param string $sessionid session id to pass through
*/
public function startAccounting($username, $sessionid)
{
// only send messages if target port specified
if ($this->acctPort != null) {
$radius = radius_auth_open();
$error = null;
if (
!radius_add_server(
$radius,
$this->radiusHost,
$this->acctPort,
$this->sharedSecret,
$this->timeout,
$this->maxRetries
)
) {
$error = radius_strerror($radius);
} elseif (!radius_create_request($radius, RADIUS_ACCOUNTING_REQUEST)) {
$error = radius_strerror($radius);
} elseif (!radius_put_string($radius, RADIUS_NAS_IDENTIFIER, $this->nasIdentifier)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_SERVICE_TYPE, RADIUS_FRAMED)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_FRAMED_PROTOCOL, RADIUS_ETHERNET)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_NAS_PORT, 0)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_NAS_PORT_TYPE, RADIUS_ETHERNET)) {
$error = radius_strerror($radius);
} elseif (!radius_put_string($radius, RADIUS_USER_NAME, $username)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_ACCT_STATUS_TYPE, RADIUS_START)) {
$error = radius_strerror($radius);
} elseif (!radius_put_string($radius, RADIUS_ACCT_SESSION_ID, $sessionid)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_ACCT_AUTHENTIC, RADIUS_AUTH_LOCAL)) {
$error = radius_strerror($radius);
}
if ($error != null) {
syslog(LOG_ERR, 'RadiusError: ' . $error);
} else {
$req = radius_send_request($radius);
if (!$req) {
syslog(LOG_ERR, 'RadiusError: ' . radius_strerror($radius));
exit;
}
switch ($req) {
case RADIUS_ACCOUNTING_RESPONSE:
break;
default:
syslog(LOG_ERR, "Unexpected return value:$radius\n");
}
radius_close($radius);
}
}
}
/**
* stop radius accounting
* @param string $username user name
* @param string $sessionid session id
* @param int $session_time total time spent on this session
* @param $bytes_in
* @param $bytes_out
* @param $ip_address
*/
public function stopAccounting($username, $sessionid, $session_time, $bytes_in, $bytes_out, $ip_address)
{
// only send messages if target port specified
if ($this->acctPort != null) {
$radius = radius_auth_open();
$error = null;
if (
!radius_add_server(
$radius,
$this->radiusHost,
$this->acctPort,
$this->sharedSecret,
$this->timeout,
$this->maxRetries
)
) {
$error = radius_strerror($radius);
} elseif (!radius_create_request($radius, RADIUS_ACCOUNTING_REQUEST)) {
$error = radius_strerror($radius);
} elseif (!radius_put_string($radius, RADIUS_NAS_IDENTIFIER, $this->nasIdentifier)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_SERVICE_TYPE, RADIUS_FRAMED)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_FRAMED_PROTOCOL, RADIUS_ETHERNET)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_NAS_PORT, 0)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_NAS_PORT_TYPE, RADIUS_ETHERNET)) {
$error = radius_strerror($radius);
} elseif (!radius_put_string($radius, RADIUS_USER_NAME, $username)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_ACCT_STATUS_TYPE, RADIUS_STOP)) {
$error = radius_strerror($radius);
} elseif (!radius_put_string($radius, RADIUS_ACCT_SESSION_ID, $sessionid)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_ACCT_AUTHENTIC, RADIUS_AUTH_LOCAL)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_ACCT_SESSION_TIME, $session_time)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_ACCT_INPUT_OCTETS, $bytes_in)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_ACCT_OUTPUT_OCTETS, $bytes_out)) {
$error = radius_strerror($radius);
} elseif (!radius_put_addr($radius, RADIUS_FRAMED_IP_ADDRESS, $ip_address)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_ACCT_TERMINATE_CAUSE, RADIUS_TERM_USER_REQUEST)) {
$error = radius_strerror($radius);
}
if ($error != null) {
syslog(LOG_ERR, 'RadiusError: ' . $error);
} else {
$req = radius_send_request($radius);
if (!$req) {
syslog(LOG_ERR, 'RadiusError: ' . radius_strerror($radius));
exit;
}
switch ($req) {
case RADIUS_ACCOUNTING_RESPONSE:
break;
default:
syslog(LOG_ERR, "Unexpected return value:$radius\n");
}
radius_close($radius);
}
}
}
/**
* update radius accounting (interim update)
* @param string $username user name
* @param string $sessionid session id
* @param int $session_time total time spend on this session
* @param $bytes_in
* @param $bytes_out
* @param $ip_address
*/
public function updateAccounting($username, $sessionid, $session_time, $bytes_in, $bytes_out, $ip_address)
{
// only send messages if target port specified
if ($this->acctPort != null) {
$radius = radius_auth_open();
if (!defined('RADIUS_UPDATE')) {
define('RADIUS_UPDATE', 3);
}
$error = null;
if (
!radius_add_server(
$radius,
$this->radiusHost,
$this->acctPort,
$this->sharedSecret,
$this->timeout,
$this->maxRetries
)
) {
$error = radius_strerror($radius);
} elseif (!radius_create_request($radius, RADIUS_ACCOUNTING_REQUEST)) {
$error = radius_strerror($radius);
} elseif (!radius_put_string($radius, RADIUS_NAS_IDENTIFIER, $this->nasIdentifier)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_SERVICE_TYPE, RADIUS_FRAMED)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_FRAMED_PROTOCOL, RADIUS_ETHERNET)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_NAS_PORT, 0)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_NAS_PORT_TYPE, RADIUS_ETHERNET)) {
$error = radius_strerror($radius);
} elseif (!radius_put_string($radius, RADIUS_USER_NAME, $username)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_ACCT_STATUS_TYPE, RADIUS_UPDATE)) {
$error = radius_strerror($radius);
} elseif (!radius_put_string($radius, RADIUS_ACCT_SESSION_ID, $sessionid)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_ACCT_AUTHENTIC, RADIUS_AUTH_LOCAL)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_ACCT_SESSION_TIME, $session_time)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_ACCT_INPUT_OCTETS, $bytes_in)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_ACCT_OUTPUT_OCTETS, $bytes_out)) {
$error = radius_strerror($radius);
} elseif (!radius_put_addr($radius, RADIUS_FRAMED_IP_ADDRESS, $ip_address)) {
$error = radius_strerror($radius);
}
if ($error != null) {
syslog(LOG_ERR, 'RadiusError: ' . $error);
} else {
$req = radius_send_request($radius);
if (!$req) {
syslog(LOG_ERR, 'RadiusError: ' . radius_strerror($radius));
exit;
}
switch ($req) {
case RADIUS_ACCOUNTING_RESPONSE:
break;
default:
syslog(LOG_ERR, "Unexpected return value:$radius\n");
}
radius_close($radius);
}
}
}
/**
* authenticate user against radius
* @param string $username username to authenticate
* @param string $password user password
* @return bool authentication status
*/
public function authenticate($username, $password)
{
$this->lastAuthProperties = array();// reset auth properties
$radius = radius_auth_open();
$error = null;
if (
!radius_add_server(
$radius,
$this->radiusHost,
$this->authPort,
$this->sharedSecret,
$this->timeout,
$this->maxRetries
)
) {
$error = radius_strerror($radius);
} elseif (!radius_create_request($radius, RADIUS_ACCESS_REQUEST)) {
$error = radius_strerror($radius);
} elseif (!radius_put_string($radius, RADIUS_USER_NAME, $username)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_SERVICE_TYPE, RADIUS_LOGIN)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_FRAMED_PROTOCOL, RADIUS_ETHERNET)) {
$error = radius_strerror($radius);
} elseif (!radius_put_string($radius, RADIUS_NAS_IDENTIFIER, $this->nasIdentifier)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_NAS_PORT, 0)) {
$error = radius_strerror($radius);
} elseif (!radius_put_int($radius, RADIUS_NAS_PORT_TYPE, RADIUS_ETHERNET)) {
$error = radius_strerror($radius);
} else {
// Implement extra protocols in this section.
switch ($this->protocol) {
case 'PAP':
// do PAP authentication
if (!radius_put_string($radius, RADIUS_USER_PASSWORD, $password)) {
$error = radius_strerror($radius);
}
break;
case 'MSCHAPv2':
require_once 'Crypt/CHAP.php';
$crpt = new \Crypt_CHAP_MSv2();
$crpt->username = $username;
$crpt->password = $password;
$resp = pack(
'CCa16a8a24',
$crpt->chapid,
1,
$crpt->peerChallenge,
str_repeat("\0", 8),
$crpt->challengeResponse()
);
if (
!radius_put_vendor_attr(
$radius,
RADIUS_VENDOR_MICROSOFT,
RADIUS_MICROSOFT_MS_CHAP_CHALLENGE,
$crpt->authChallenge
)
) {
$error = radius_strerror($radius);
} elseif (
!radius_put_vendor_attr(
$radius,
RADIUS_VENDOR_MICROSOFT,
RADIUS_MICROSOFT_MS_CHAP2_RESPONSE,
$resp
)
) {
$error = radius_strerror($radius);
}
break;
default:
syslog(LOG_ERR, 'Unsupported protocol ' . $this->protocol);
return false;
}
}
// log errors and perform actual authentication request
if ($error != null) {
syslog(LOG_ERR, 'RadiusError: ' . $error);
} else {
$request = radius_send_request($radius);
if (!$radius) {
syslog(LOG_ERR, 'RadiusError: ' . radius_strerror($radius));
} else {
switch ($request) {
case RADIUS_ACCESS_ACCEPT:
while ($resa = radius_get_attr($radius)) {
switch ($resa['attr']) {
case RADIUS_SESSION_TIMEOUT:
$this->lastAuthProperties['session_timeout'] = radius_cvt_int($resa['data']);
break;
case 85: // Acct-Interim-Interval
$this->lastAuthProperties['Acct-Interim-Interval'] = radius_cvt_int($resa['data']);
break;
case RADIUS_FRAMED_IP_ADDRESS:
$this->lastAuthProperties['Framed-IP-Address'] = radius_cvt_addr($resa['data']);
break;
case RADIUS_FRAMED_IP_NETMASK:
$this->lastAuthProperties['Framed-IP-Netmask'] = radius_cvt_addr($resa['data']);
break;
case RADIUS_FRAMED_ROUTE:
if (empty($this->lastAuthProperties['Framed-Route'])) {
$this->lastAuthProperties['Framed-Route'] = array();
}
$this->lastAuthProperties['Framed-Route'][] = $resa['data'];
break;
case RADIUS_CLASS:
if (!$this->syncMemberOf) {
break;
} elseif (!empty($this->lastAuthProperties['class'])) {
$this->lastAuthProperties['class'] .= "\n" . $resa['data'];
} else {
$this->lastAuthProperties['class'] = $resa['data'];
}
break;
default:
break;
}
}
// update group policies when applicable
if ($this->syncMemberOf || $this->syncCreateLocalUsers) {
$this->setGroupMembership(
$username,
$this->lastAuthProperties['class'] ?? '',
$this->syncMemberOf ? $this->syncMemberOfLimit : $this->syncDefaultGroups,
$this->syncCreateLocalUsers,
$this->syncDefaultGroups
);
}
return true;
break;
case RADIUS_ACCESS_REJECT:
return false;
break;
default:
// unexpected result, log
syslog(LOG_ERR, 'Radius unexpected response:' . $request);
}
}
}
return false;
}
}