VPN: IPsec: Mobile Clients - move charon attributes to "Advanced settings" for https://github.com/opnsense/core/issues/8349 (#8380)

Rename previous "advanced settings" to "mobile & advanced settings" to guide people into the right direction, strongswan.conf contains both sets of data.
Keep legacy page for settings that are only relevant for the old components.

Since our pam authenticator hooks into the configuration, refactor to use the model as well.

Cleanup code in the model that was only used in the legacy glue.
This commit is contained in:
Ad Schellevis 2025-02-28 12:53:25 +00:00 committed by GitHub
parent 387c381300
commit a893cdc7a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 418 additions and 482 deletions

1
plist
View File

@ -786,6 +786,7 @@
/usr/local/opnsense/mvc/app/models/OPNsense/IPsec/Migrations/M1_0_1.php
/usr/local/opnsense/mvc/app/models/OPNsense/IPsec/Migrations/M1_0_2.php
/usr/local/opnsense/mvc/app/models/OPNsense/IPsec/Migrations/M1_0_3.php
/usr/local/opnsense/mvc/app/models/OPNsense/IPsec/Migrations/M1_0_4.php
/usr/local/opnsense/mvc/app/models/OPNsense/IPsec/Swanctl.php
/usr/local/opnsense/mvc/app/models/OPNsense/IPsec/Swanctl.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Interfaces/ACL/ACL.xml

View File

@ -1,7 +1,7 @@
<?php
/*
* Copyright (C) 2016-2023 Deciso B.V.
* Copyright (C) 2016-2025 Deciso B.V.
* Copyright (C) 2019 Pascal Mathis <mail@pascalmathis.com>
* Copyright (C) 2008 Shrew Soft Inc. <mgrooms@shrew.net>
* Copyright (C) 2008 Ermal Luçi
@ -925,6 +925,8 @@ function ipsec_write_strongswan_conf()
$strongswanTree = (new \OPNsense\IPsec\IPsec())->strongswanTree();
/* legacy overwrites for strongswan.conf */
foreach ($a_phase1 as $ph1ent) {
if (isset($ph1ent['disabled'])) {
continue;
@ -936,18 +938,10 @@ function ipsec_write_strongswan_conf()
}
}
$strongswanTree['charon']['install_routes'] = 'no';
if (isset($a_client['enable']) && isset($a_client['net_list'])) {
$strongswanTree['charon']['cisco_unity'] = 'yes';
}
$strongswanTree['charon']['plugins'] = [];
$radius_auth_servers = null;
$disable_xauth = false;
if (isset($a_client['enable'])) {
if (isset($a_client['enable']) && empty($strongswanTree['charon']['plugins']['attr']['subnet'])) {
/* legacy subnet collection, can only be used when not offered manually */
$net_list = [];
if (isset($a_client['net_list'])) {
if ($strongswanTree['charon']['cisco_unity'] == 'yes') {
foreach ($a_phase1 as $ph1ent) {
if (isset($ph1ent['disabled']) || !isset($ph1ent['mobile'])) {
continue;
@ -960,123 +954,21 @@ function ipsec_write_strongswan_conf()
}
}
$strongswanTree['charon']['plugins']['attr'] = [];
if (!isset($strongswanTree['charon']['plugins']['attr'])) {
$strongswanTree['charon']['plugins']['attr'] = [];
}
if (!empty($net_list)) {
$net_list_str = implode(",", $net_list);
$strongswanTree['charon']['plugins']['attr']['subnet'] = $net_list_str;
$strongswanTree['charon']['plugins']['attr']['split-include'] = $net_list_str;
}
$cfgservers = [];
foreach (array('dns_server1', 'dns_server2', 'dns_server3', 'dns_server4') as $dns_server) {
if (!empty($a_client[$dns_server])) {
$cfgservers[] = $a_client[$dns_server];
}
}
if (!empty($cfgservers)) {
$strongswanTree['charon']['plugins']['attr']['dns'] = implode(",", $cfgservers);
}
$cfgservers = [];
if (!empty($a_client['wins_server1'])) {
$cfgservers[] = $a_client['wins_server1'];
}
if (!empty($a_client['wins_server2'])) {
$cfgservers[] = $a_client['wins_server2'];
}
if (!empty($cfgservers)) {
$strongswanTree['charon']['plugins']['attr']['nbns'] = implode(",", $cfgservers);
}
if (!empty($a_client['dns_domain'])) {
$strongswanTree['charon']['plugins']['attr']['# Search domain and default domain'] = '';
$strongswanTree['charon']['plugins']['attr']['28674'] = $a_client['dns_domain'];
}
/*
* 28675 --> UNITY_SPLITDNS_NAME
* 25 --> INTERNAL_DNS_DOMAIN
*/
foreach (array("28675", "25") as $attr) {
if (!empty($a_client['dns_split'])) {
$strongswanTree['charon']['plugins']['attr'][$attr] = $a_client['dns_split'];
} elseif (!empty($a_client['dns_domain'])) {
$strongswanTree['charon']['plugins']['attr'][$attr] = $a_client['dns_domain'];
}
}
if (!empty($a_client['dns_split'])) {
$strongswanTree['charon']['plugins']['attr']['28675'] = $a_client['dns_split'];
}
if (!empty($a_client['login_banner'])) {
/* defang login banner, it may be multiple lines and we should not let it escape */
$strongswanTree['charon']['plugins']['attr']['28672'] = '"' . str_replace(['\\', '"'], '', $a_client['login_banner']) . '"';
}
if (isset($a_client['save_passwd'])) {
$strongswanTree['charon']['plugins']['attr']['28673'] = 1;
}
if (!empty($a_client['pfs_group'])) {
$strongswanTree['charon']['plugins']['attr']['28679'] = $a_client['pfs_group'];
}
foreach ($a_phase1 as $ph1ent) {
if (!isset($ph1ent['disabled']) && isset($ph1ent['mobile'])) {
if ($ph1ent['authentication_method'] == "eap-radius") {
$radius_auth_servers = $ph1ent['authservers'];
break; // there can only be one mobile phase1, exit loop
}
}
}
}
if (empty($radius_auth_servers) && !empty($a_client['radius_source'])) {
$radius_auth_servers = $a_client['radius_source'];
}
$mdl = new \OPNsense\IPsec\Swanctl();
if ((isset($a_client['enable']) || $mdl->isEnabled()) && !empty($radius_auth_servers)) {
$disable_xauth = true; // disable Xauth when radius is used.
$strongswanTree['charon']['plugins']['eap-radius'] = [];
$strongswanTree['charon']['plugins']['eap-radius']['servers'] = [];
$radius_server_num = 1;
$radius_accounting_enabled = false;
foreach (auth_get_authserver_list() as $auth_server) {
if (in_array($auth_server['name'], explode(',', $radius_auth_servers))) {
$server = [
'address' => $auth_server['host'],
'secret' => '"' . $auth_server['radius_secret'] . '"',
'auth_port' => $auth_server['radius_auth_port'],
];
if (!empty($auth_server['radius_acct_port'])) {
$server['acct_port'] = $auth_server['radius_acct_port'];
}
$strongswanTree['charon']['plugins']['eap-radius']['servers']['server' . $radius_server_num] = $server;
if (!empty($auth_server['radius_acct_port'])) {
$radius_accounting_enabled = true;
}
$radius_server_num += 1;
}
}
if ($radius_accounting_enabled) {
$strongswanTree['charon']['plugins']['eap-radius']['accounting'] = 'yes';
}
if ($mdl->radiusUsesGroups()) {
$strongswanTree['charon']['plugins']['eap-radius']['class_group'] = 'yes';
}
}
if ((isset($a_client['enable']) && !$disable_xauth) || (new \OPNsense\IPsec\Swanctl())->isEnabled()) {
$strongswanTree['charon']['plugins']['xauth-pam'] = [
'pam_service' => 'ipsec',
'session' => 'no',
'trim_email' => 'yes'
];
}
$strongswan = generate_strongswan_conf($strongswanTree);
$strongswan .= "\ninclude strongswan.opnsense.d/*.conf\n";
@file_put_contents("/usr/local/etc/strongswan.conf", $strongswan);
/* flush to disk */
@file_put_contents(
"/usr/local/etc/strongswan.conf",
sprintf("%s\ninclude strongswan.opnsense.d/*.conf\n", generate_strongswan_conf($strongswanTree))
);
}
/**

View File

@ -28,6 +28,59 @@
When sending all traffic to the remote location, you probably want to add your lan network(s) here.
</help>
</field>
<field>
<id>ipsec.general.user_source</id>
<label>Authentication (Xauth)</label>
<type>select_multiple</type>
<style>selectpicker</style>
<help>Select authentication methods to use, leave empty if no challenge response authentication is needed.</help>
</field>
<field>
<id>ipsec.general.local_group</id>
<label>Enforce local group (Xauth)</label>
<type>dropdown</type>
<style>selectpicker</style>
<help>Restrict access to users in the selected local group. Please be aware that other authentication backends will refuse to authenticate when using this option.</help>
</field>
</tab>
<tab id="ipsec-eap-radius" description="eap-radius">
<field>
<id>ipsec.charon.plugins.eap-radius.servers</id>
<label>Servers</label>
<type>select_multiple</type>
<help>RADIUS servers to configure</help>
</field>
<field>
<id>ipsec.charon.plugins.eap-radius.accounting</id>
<label>Accounting</label>
<type>checkbox</type>
<help>Enable RADIUS accounting</help>
</field>
<field>
<id>ipsec.charon.plugins.eap-radius.class_group</id>
<label>Group selection (class_group)</label>
<type>checkbox</type>
</field>
</tab>
<tab id="ipsec-xauth-pam" description="xauth-pam">
<field>
<id>ipsec.charon.plugins.xauth-pam.pam_service</id>
<label>Pam_service</label>
<type>text</type>
<help>PAM service to use for authentication.</help>
</field>
<field>
<id>ipsec.charon.plugins.xauth-pam.session</id>
<label>Session</label>
<type>checkbox</type>
<help>Open/close a PAM session for each active IKE_SA</help>
</field>
<field>
<id>ipsec.charon.plugins.xauth-pam.trim_email</id>
<label>Trim_email</label>
<type>checkbox</type>
<help>If an email address is received as an XAuth username, trim it to just the username part</help>
</field>
</tab>
<tab id="ipsec-charon" description="Charon">
<field>
@ -84,6 +137,18 @@
<type>checkbox</type>
<help>Initiate IKEv2 reauthentication with a make-before-break instead of a break-before-make scheme. Make-before-break uses overlapping IKE and CHILD SA during reauthentication by first recreating all new SAs before deleting the old ones. This behavior can be beneficial to avoid connectivity gaps during reauthentication, but requires support for overlapping SAs by the peer.</help>
</field>
<field>
<id>ipsec.charon.install_routes</id>
<label>Install routes</label>
<type>checkbox</type>
<help>Install routes into a separate routing table for established IPsec tunnels. If disabled a more efficient lookup for source and next-hop addresses is used.</help>
</field>
<field>
<id>ipsec.charon.cisco_unity</id>
<label>Cisco Unity</label>
<type>checkbox</type>
<help>Send Cisco Unity vendor ID payload (IKEv1 only).</help>
</field>
<field>
<type>header</type>
<label>Retransmission</label>
@ -231,5 +296,72 @@
<type>dropdown</type>
</field>
</tab>
<tab id="ipsec-attr" description="Attr">
<field>
<id>ipsec.charon.plugins.attr.subnet</id>
<label>Subnet</label>
<type>select_multiple</type>
<style>tokenize</style>
<allownew>true</allownew>
<help>The protected sub-networks that this edge-device protects (in CIDR notation). Usually ignored in deference to local_ts, though macOS clients will use this for routes</help>
</field>
<field>
<id>ipsec.charon.plugins.attr.dns</id>
<label>DNS</label>
<type>select_multiple</type>
<style>tokenize</style>
<allownew>true</allownew>
<help>DNS server</help>
</field>
<field>
<id>ipsec.charon.plugins.attr.nbns</id>
<label>NBNS</label>
<type>select_multiple</type>
<style>tokenize</style>
<allownew>true</allownew>
<help>WINS server</help>
</field>
<field>
<type>header</type>
<label>Cisco Unity</label>
</field>
<field>
<id>ipsec.charon.plugins.attr.split-include</id>
<label>Split-include</label>
<type>select_multiple</type>
<style>tokenize</style>
<allownew>true</allownew>
<help>Comma-separated list of subnets to tunnel. The unity plugin provides a connection specific approach to assign this attribute.</help>
</field>
<field>
<id>ipsec.charon.plugins.attr.x_28674</id>
<label>Default search</label>
<type>text</type>
<help>Default search domain used when resolving host names via the assigned DNS servers</help>
</field>
<field>
<id>ipsec.charon.plugins.attr.x_28675</id>
<label>Split DNS name</label>
<type>text</type>
<help>If split tunneling is used clients might not install the assigned DNS servers globally. This space-separated list of domain names allows clients, such as macOS, to selectively query the assigned DNS servers. Seems Mac OS X uses only the first item in the list</help>
</field>
<field>
<id>ipsec.charon.plugins.attr.x_28672</id>
<label>Login banner</label>
<type>textbox</type>
<help>Message displayed on certain clients after login</help>
</field>
<field>
<id>ipsec.charon.plugins.attr.x_28673</id>
<label>Save password</label>
<type>checkbox</type>
<help>Allow client to save Xauth password in local storage</help>
</field>
<field>
<id>ipsec.charon.plugins.attr.x_28679</id>
<label>PFS group</label>
<type>dropdown</type>
</field>
</tab>
<activetab>ipsec-general</activetab>
</form>

View File

@ -56,10 +56,10 @@ class IPsec implements IService
*/
public function supportedAuthenticators()
{
$result = array();
$configObj = Config::getInstance()->object();
if (!empty((string)$configObj->ipsec->client->user_source)) {
$result = explode(',', (string)$configObj->ipsec->client->user_source);
$result = [];
$mdl = new \OPNsense\IPsec\IPsec();
if (!empty((string)$mdl->general->user_source)) {
$result = explode(',', (string)$mdl->general->user_source);
} else {
$result[] = 'Local Database';
}
@ -87,11 +87,11 @@ class IPsec implements IService
*/
public function checkConstraints()
{
$configObj = Config::getInstance()->object();
if (!empty((string)$configObj->ipsec->client->local_group)) {
$mdl = new \OPNsense\IPsec\IPsec();
if (!empty((string)$mdl->general->local_group)) {
// Enforce group constraint when set
$local_group = (string)$configObj->ipsec->client->local_group;
return (new ACL())->inGroup($this->getUserName(), $local_group);
$local_group = (string)$mdl->general->local_group;
return (new ACL())->inGroup($this->getUserName(), $local_group, false);
} else {
// no constraints
return true;

View File

@ -1,7 +1,7 @@
<?php
/*
* Copyright (C) 2022 Deciso B.V.
* Copyright (C) 2022-2025 Deciso B.V.
* Copyright (C) 2019 Pascal Mathis <mail@pascalmathis.com>
* All rights reserved.
*
@ -31,6 +31,8 @@ namespace OPNsense\IPsec;
use OPNsense\Base\Messages\Message;
use OPNsense\Base\BaseModel;
use OPNsense\Core\Config;
/**
* Class IPsec
@ -187,13 +189,45 @@ class IPsec extends BaseModel
private function traverseItems($node)
{
$result = [];
$cnf = Config::getInstance()->object();
foreach ($node->iterateItems() as $key => $item) {
$is_numeric = str_starts_with($key, 'x_');
/* numeric keys, need to rename for valid xml */
$target_key = $is_numeric ? substr($key, 2) : $key;
if ($item->isContainer()) {
$result[$key] = $this->traverseItems($item);
$result[$target_key] = $this->traverseItems($item);
} elseif (is_a($item, "OPNsense\\Base\\FieldTypes\\BooleanField")) {
$result[$key] = !empty((string)$item) ? 'yes' : 'no';
$result[$target_key] = !empty((string)$item) ? 'yes' : 'no';
} elseif (is_a($item, "OPNsense\\Base\\FieldTypes\\AuthenticationServerField")) {
$servers = [];
foreach(explode(',', (string)$item) as $item) {
$idx = 'server' . (string)(count($servers) + 1);
$mapping = [];
if (isset($cnf->authserver)) {
foreach ($cnf->authserver as $authserver) {
if ($authserver->name == $item) {
$servers[$idx] = [
'address' => (string)$authserver->host,
'secret' => '"' . (string)$authserver->radius_secret . '"',
'auth_port' => (string)$authserver->radius_auth_port,
];
if (!empty((string)$authserver->radius_acct_port)) {
$servers[$idx]['acct_port'] = (string)$authserver->radius_acct_port;
}
}
}
}
}
$result[$target_key] = $servers;
} elseif ((string)$item != '') {
$result[$key] = (string)$item;
if ($target_key == '28672') {
/* Unity login banner, needs to be wrapped? */
$result[$target_key] = '"' . str_replace(['\\', '"'], '', (string)$item) . '"';
} else {
$result[$target_key] = (string)$item;
}
}
}
return $result;

View File

@ -1,6 +1,6 @@
<model>
<mount>//OPNsense/IPsec</mount>
<version>1.0.3</version>
<version>1.0.4</version>
<description>OPNsense IPsec</description>
<items>
<general>
@ -20,6 +20,10 @@
<asList>Y</asList>
<WildcardEnabled>N</WildcardEnabled>
</passthrough_networks>
<user_source type="AuthenticationServerField">
<Multiple>Y</Multiple>
</user_source>
<local_group type="AuthGroupField"/>
</general>
<charon>
<max_ikev1_exchanges type="IntegerField">
@ -55,6 +59,8 @@
<Default>1</Default>
<Required>Y</Required>
</ignore_acquire_ts>
<install_routes type="BooleanField"/>
<cisco_unity type="BooleanField"/>
<make_before_break type="BooleanField"/>
<retransmit_tries type="IntegerField"/>
<retransmit_timeout type="NumericField"/>
@ -92,6 +98,82 @@
<tnc type=".\CharonLogLevelField"/>
</daemon>
</syslog>
<plugins>
<attr>
<subnet type="NetworkField">
<NetMaskRequired>Y</NetMaskRequired>
<FieldSeparator>,</FieldSeparator>
<asList>Y</asList>
</subnet>
<split-include type="NetworkField">
<NetMaskRequired>Y</NetMaskRequired>
<FieldSeparator>,</FieldSeparator>
<asList>Y</asList>
</split-include>
<!-- UNITY_DEF_DOMAIN -->
<x_28674 type="TextField"/>
<x_28675 type="TextField"/>
<x_25 type="TextField"/>
<x_28672 type="TextField"/>
<x_28673 type="BooleanField"/>
<x_28679 type="OptionField">
<OptionValues>
<o1 value='1'>1 (768 bits)</o1 >
<o2 value='2'>2 (1024 bits)</o2 >
<o5 value='5'>5 (1536 bits)</o5 >
<o14 value='14'>14 (2048 bits)</o14 >
<o15 value='15'>15 (3072 bits)</o15 >
<o16 value='16'>16 (4096 bits)</o16 >
<o17 value='17'>17 (6144 bits)</o17 >
<o18 value='18'>18 (8192 bits)</o18 >
<o19 value='19'>19 (NIST EC 256 bits)</o19 >
<o20 value='20'>20 (NIST EC 384 bits)</o20 >
<o21 value='21'>21 (NIST EC 521 bits)</o21 >
<o22 value='22'>22 (1024(sub 160) bits)</o22 >
<o23 value='23'>23 (2048(sub 224) bits)</o23 >
<o24 value='24'>24 (2048(sub 256) bits)</o24 >
<o28 value='28'>28 (Brainpool EC 256 bits)</o28 >
<o29 value='29'>29 (Brainpool EC 384 bits)</o29 >
<o30 value='30'>30 (Brainpool EC 512 bits)</o30 >
<o31 value='31'>31 (Elliptic Curve 25519)</o31 >
</OptionValues>
</x_28679>
<dns type="NetworkField">
<NetmaskAllowed>N</NetmaskAllowed>
<FieldSeparator>,</FieldSeparator>
<asList>Y</asList>
</dns>
<nbns type="NetworkField">
<NetmaskAllowed>N</NetmaskAllowed>
<FieldSeparator>,</FieldSeparator>
<asList>Y</asList>
</nbns>
</attr>
<eap-radius>
<servers type="AuthenticationServerField">
<Multiple>Y</Multiple>
<filters>
<type>/^(radius)$/</type>
</filters>
</servers>
<accounting type="BooleanField"/>
<class_group type="BooleanField"/>
</eap-radius>
<xauth-pam>
<pam_service type="TextField">
<Required>Y</Required>
<Default>ipsec</Default>
</pam_service>
<session type="BooleanField">
<Required>Y</Required>
<Default>0</Default>
</session>
<trim_email type="BooleanField">
<Required>Y</Required>
<Default>1</Default>
</trim_email>
</xauth-pam>
</plugins>
</charon>
<keyPairs>
<keyPair type="ArrayField">

View File

@ -6,12 +6,12 @@
<Phase1 url="/vpn_ipsec_phase1.php*" visibility="hidden"/>
<Phase2 url="/vpn_ipsec_phase2.php*" visibility="hidden"/>
</Tunnels>
<Mobile order="20" VisibleName="Mobile Clients" url="/vpn_ipsec_mobile.php">
<Mobile order="20" VisibleName="Mobile Clients (legacy)" url="/vpn_ipsec_mobile.php">
<Act url="/vpn_ipsec_mobile.php*" visibility="hidden"/>
</Mobile>
<Keys order="30" VisibleName="Pre-Shared Keys" url="/ui/ipsec/pre_shared_keys/"/>
<KeyPairs order="40" VisibleName="Key Pairs" url="/ui/ipsec/key_pairs" />
<Settings order="50" VisibleName="Advanced Settings" url="/ui/ipsec/connections/settings"/>
<Settings order="50" VisibleName="Mobile &amp; Advanced Settings" url="/ui/ipsec/connections/settings"/>
<Status order="60" VisibleName="Status Overview" url="/ui/ipsec/sessions"/>
<Leases order="70" VisibleName="Lease Status" url="/ui/ipsec/leases"/>
<SAD order="80" VisibleName="Security Association Database" url="/ui/ipsec/sad"/>

View File

@ -0,0 +1,137 @@
<?php
/*
* Copyright (C) 2025 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\IPsec\Migrations;
use OPNsense\Base\BaseModelMigration;
use OPNsense\Core\Config;
use OPNsense\IPsec\IPsec;
class M1_0_4 extends BaseModelMigration
{
public function run($model)
{
if (!$model instanceof IPsec) {
return;
}
$cnf = Config::getInstance()->object();
if (!isset($cnf->ipsec) || !isset($cnf->ipsec->client)) {
return;
}
if (isset($cnf->ipsec->client) && isset($cnf->ipsec->client->net_list)) {
$model->charon->cisco_unity = '1';
unset($cnf->ipsec->client->net_list);
}
$dns_servers = [];
foreach (['dns_server1', 'dns_server2', 'dns_server3', 'dns_server4'] as $tmp) {
if (!empty((string)$cnf->ipsec->client->$tmp)) {
$dns_servers[] = (string)$cnf->ipsec->client->$tmp;
unset($cnf->ipsec->client->$tmp);
}
}
if (!empty($dns_servers)) {
$model->charon->plugins->attr->dns = implode(',', $dns_servers);
}
$nbns_servers = [];
foreach (['wins_server1', 'wins_server2'] as $tmp) {
if (!empty((string)$cnf->ipsec->client->$tmp)) {
$nbns_servers[] = (string)$cnf->ipsec->client->$tmp;
unset($cnf->ipsec->client->$tmp);
}
}
if (!empty($nbns_servers)) {
$model->charon->plugins->attr->nbns = implode(',', $nbns_servers);
}
if (!empty((string)$cnf->ipsec->client->dns_domain)) {
$model->charon->plugins->attr->x_28674 = (string)$cnf->ipsec->client->dns_domain;
$model->charon->plugins->attr->x_28675 = (string)$cnf->ipsec->client->dns_domain;
unset($cnf->ipsec->client->dns_domain);
}
if (!empty((string)$cnf->ipsec->client->dns_split)) {
/* overwrites previous when both are set */
$model->charon->plugins->attr->x_28675 = (string)$cnf->ipsec->client->dns_split;
unset($cnf->ipsec->client->dns_split);
}
if (!empty((string)$cnf->ipsec->client->login_banner)) {
$model->charon->plugins->attr->x_28672 = (string)$cnf->ipsec->client->login_banner;
unset($cnf->ipsec->client->login_banner);
}
if (isset($cnf->ipsec->client->save_passwd)) {
$model->charon->plugins->attr->x_28673 = '1';
unset($cnf->ipsec->client->save_passwd);
}
if (!empty((string)$cnf->ipsec->client->pfs_group)) {
$model->charon->plugins->attr->x_28679 = (string)$cnf->ipsec->client->pfs_group;
unset($cnf->ipsec->client->pfs_group);
}
if (!empty((string)$cnf->ipsec->client->radius_source)) {
$model->charon->plugins->{'eap-radius'}->servers = (string)$cnf->ipsec->client->radius_source;
unset($cnf->ipsec->client->radius_source);
} else {
if (isset($cnf->ipsec->phase1)) {
foreach ($cnf->ipsec->phase1 as $phase1) {
if (!isset($phase1->disabled) && isset($phase1->mobile) &&
$phase1->authentication_method == 'eap-radius'
) {
$model->charon->plugins->{'eap-radius'}->servers = (string)$phase1->authservers;
}
}
}
}
if (!empty((string)$cnf->ipsec->client->user_source)) {
$tmp = explode(',', (string)$cnf->ipsec->client->user_source);
$user_source = [];
foreach ($model->general->user_source->getNodeData() as $key => $data) {
if (in_array($key, $tmp)) {
$user_source[] = $key;
}
}
if (!empty($user_source)) {
$model->general->user_source = implode(',', $user_source);
}
unset($cnf->ipsec->client->user_source);
}
if (!empty((string)$cnf->ipsec->client->local_group)) {
foreach ($model->general->local_group->getNodeData() as $key => $data) {
if ((string)$cnf->ipsec->client->local_group == $data['value']) {
$model->general->local_group = $key;
}
}
unset($cnf->ipsec->client->local_group);
}
}
}

View File

@ -337,25 +337,4 @@ class Swanctl extends BaseModel
}
return $certrefs;
}
/**
* @return bool is there at least one connection using radius groups?
*/
public function radiusUsesGroups()
{
foreach ($this->remotes->iterateRecursiveItems() as $node) {
if ($node->getInternalXMLTagName() == 'auth' && (string)$node == 'eap-radius') {
$auth = $node->getParentNode();
$connid = (string)$auth->connection;
if (
!empty((string)$auth->groups) &&
isset($this->Connections->Connection->$connid) &&
!empty((string)$this->Connections->Connection->$connid->enabled)
) {
return true;
}
}
}
return false;
}
}

View File

@ -1,7 +1,7 @@
<?php
/*
* Copyright (C) 2014-2023 Deciso B.V.
* Copyright (C) 2014-2025 Deciso B.V.
* Copyright (C) 2008 Shrew Soft Inc. <mgrooms@shrew.net>
* All rights reserved.
*
@ -37,9 +37,7 @@ config_read_array('ipsec', 'client');
config_read_array('ipsec', 'phase1');
// define formfields
$form_fields = "user_source,local_group,radius_source,pool_address,pool_netbits,pool_address_v6,pool_netbits_v6,net_list
,save_passwd,dns_domain,dns_split,dns_server1,dns_server2,dns_server3
,dns_server4,wins_server1,wins_server2,pfs_group,login_banner";
$form_fields = "pool_address,pool_netbits,pool_address_v6,pool_netbits_v6";
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// pass savemessage
@ -64,13 +62,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
if (isset($config['ipsec']['client']['enable'])) {
$pconfig['enable'] = true;
}
if (isset($config['ipsec']['client']['net_list'])) {
$pconfig['net_list'] = true;
}
if (isset($config['ipsec']['client']['save_passwd'])) {
$pconfig['save_passwd'] = true;
}
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input_errors = array();
$pconfig = $_POST;
@ -87,20 +79,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
exit;
} elseif (isset($_POST['submit'])) {
// save form changes
// input preparations
if (!empty($pconfig['user_source'])) {
$pconfig['user_source'] = implode(",", $pconfig['user_source']);
}
if (!empty($pconfig['radius_source'])) {
$pconfig['radius_source'] = implode(",", $pconfig['radius_source']);
}
/* input validation */
$reqdfields = explode(" ", "user_source");
$reqdfieldsn = array(gettext("User Authentication Source"));
do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
if (!empty($pconfig['pool_address']) && !is_ipaddr($pconfig['pool_address'])) {
$input_errors[] = gettext("A valid IPv4 address for 'Virtual IPv4 Address Pool Network' must be specified.");
}
@ -109,45 +87,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$input_errors[] = gettext("A valid IPv6 address for 'Virtual IPv6 Address Pool Network' must be specified.");
}
if (!empty($pconfig['dns_domain']) && !is_domain($pconfig['dns_domain'])) {
$input_errors[] = gettext("A valid value for 'DNS Default Domain' must be specified.");
}
if (!empty($pconfig['dns_split'])) {
$domain_array=preg_split("/[ ,]+/", $pconfig['dns_split']);
foreach ($domain_array as $curdomain) {
if (!is_domain($curdomain)) {
$input_errors[] = gettext("A valid split DNS domain list must be specified.");
break;
}
}
}
if (!empty($pconfig['dns_server1']) && !is_ipaddr($pconfig['dns_server1'])) {
$input_errors[] = gettext("A valid IP address for 'DNS Server #1' must be specified.");
}
if (!empty($pconfig['dns_server2']) && !is_ipaddr($pconfig['dns_server2'])) {
$input_errors[] = gettext("A valid IP address for 'DNS Server #2' must be specified.");
}
if (!empty($pconfig['dns_server3']) && !is_ipaddr($pconfig['dns_server3'])) {
$input_errors[] = gettext("A valid IP address for 'DNS Server #3' must be specified.");
}
if (!empty($pconfig['dns_server4']) && !is_ipaddr($pconfig['dns_server4'])) {
$input_errors[] = gettext("A valid IP address for 'DNS Server #4' must be specified.");
}
if (!empty($pconfig['wins_server1']) && !is_ipaddr($pconfig['wins_server1'])) {
$input_errors[] = gettext("A valid IP address for 'WINS Server #1' must be specified.");
}
if (!empty($pconfig['wins_server2']) && !is_ipaddr($pconfig['wins_server2'])) {
$input_errors[] = gettext("A valid IP address for 'WINS Server #2' must be specified.");
}
if (count($input_errors) == 0) {
$client = array();
$copy_fields = "user_source,local_group,radius_source,pool_address,pool_netbits,pool_address_v6,
pool_netbits_v6,dns_domain,dns_server1,dns_server2,dns_server3,dns_server4,wins_server1,wins_server2
,dns_split,pfs_group,login_banner";
$copy_fields = "pool_address,pool_netbits,pool_address_v6,pool_netbits_v6";
foreach (explode(",", $copy_fields) as $fieldname) {
$fieldname = trim($fieldname);
if (!empty($pconfig[$fieldname])) {
@ -158,14 +101,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$client['enable'] = true;
}
if (!empty($pconfig['net_list'])) {
$client['net_list'] = true;
}
if (!empty($pconfig['save_passwd'])) {
$client['save_passwd'] = true;
}
$config['ipsec']['client'] = $client;
write_config();
@ -199,11 +134,6 @@ include("head.inc");
$( document ).ready(function() {
pool_change();
pool_v6_change();
dns_domain_change();
dns_split_change();
dns_server_change();
wins_server_change();
login_banner_change();
$("#ike_mobile_enable").change(function(){
if ($(this).is(':checked')) {
@ -238,80 +168,6 @@ function pool_v6_change() {
}
}
function dns_domain_change() {
if (document.iform.dns_domain_enable.checked) {
document.iform.dns_domain.disabled = 0;
$("#dns_domain").addClass('show');
$("#dns_domain").removeClass('hidden');
} else {
document.iform.dns_domain.disabled = 1;
$("#dns_domain").addClass('hidden');
$("#dns_domain").removeClass('show');
}
}
function dns_split_change() {
if (document.iform.dns_split_enable.checked){
document.iform.dns_split.disabled = 0;
$("#dns_split").addClass('show');
$("#dns_split").removeClass('hidden');
} else {
document.iform.dns_split.disabled = 1;
$("#dns_split").addClass('hidden');
$("#dns_split").removeClass('show');
}
}
function dns_server_change() {
if (document.iform.dns_server_enable.checked) {
document.iform.dns_server1.disabled = 0;
document.iform.dns_server2.disabled = 0;
document.iform.dns_server3.disabled = 0;
document.iform.dns_server4.disabled = 0;
$("#dns_server_enable_inputs").addClass('show');
$("#dns_server_enable_inputs").removeClass('hidden');
} else {
document.iform.dns_server1.disabled = 1;
document.iform.dns_server2.disabled = 1;
document.iform.dns_server3.disabled = 1;
document.iform.dns_server4.disabled = 1;
$("#dns_server_enable_inputs").addClass('hidden');
$("#dns_server_enable_inputs").removeClass('show');
}
}
function wins_server_change() {
if (document.iform.wins_server_enable.checked) {
document.iform.wins_server1.disabled = 0;
document.iform.wins_server2.disabled = 0;
$("#wins_server_enable_inputs").addClass('show');
$("#wins_server_enable_inputs").removeClass('hidden');
} else {
document.iform.wins_server1.disabled = 1;
document.iform.wins_server2.disabled = 1;
$("#wins_server_enable_inputs").addClass('hidden');
$("#wins_server_enable_inputs").removeClass('show');
}
}
function login_banner_change() {
if (document.iform.login_banner_enable.checked) {
document.iform.login_banner.disabled = 0;
$("#login_banner").addClass('show');
$("#login_banner").removeClass('hidden');
} else {
document.iform.login_banner.disabled = 1;
$("#login_banner").addClass('hidden');
$("#login_banner").removeClass('show');
}
}
//]]>
</script>
@ -366,69 +222,6 @@ if (isset($input_errors) && count($input_errors) > 0) {
}
?>
<form method="post" name="iform" id="iform">
<section class="col-xs-12">
<div class="tab-content content-box col-xs-12">
<div class="table-responsive">
<table class="table table-striped opnsense_standard_table_form">
<tr>
<td colspan="2"><b><?=gettext("Extended Authentication (Xauth)"); ?></b></td>
</tr>
<tr>
<td style="width:22%"><i class="fa fa-info-circle text-muted"></i> <?=gettext("Backend for authentication");?> </td>
<td style="width:78%">
<select name="user_source[]" class="selectpicker" id="user_source" multiple="multiple" size="3">
<?php
$authmodes = explode(",", $pconfig['user_source']);
$auth_servers = auth_get_authserver_list();
foreach ($auth_servers as $auth_key => $auth_server) : ?>
<option value="<?=htmlspecialchars($auth_key)?>" <?=in_array($auth_key, $authmodes) ? 'selected="selected"' : ''?>><?=htmlspecialchars($auth_server['name'])?></option>
<?php endforeach; ?>
</select>
</td>
</tr>
<tr>
<td><a id="help_for_local_group" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?= gettext('Enforce local group') ?></td>
<td>
<select name="local_group" class="selectpicker" id="local_group">
<option value="" <?= empty($pconfig['local_group']) ? 'selected="selected"' : '' ?>>(<?= gettext('none') ?>)</option>
<?php
foreach (config_read_array('system', 'group') as $group):
$selected = $pconfig['local_group'] == $group['name'] ? 'selected="selected"' : ''; ?>
<option value="<?= $group['name'] ?>" <?= $selected ?>><?= $group['name'] ?></option>
<?php
endforeach ?>
</select>
<div class="hidden" data-for="help_for_local_group">
<?= gettext('Restrict access to users in the selected local group. Please be aware ' .
'that other authentication backends will refuse to authenticate when using this option.') ?>
</div>
</td>
</tr>
<tr>
<td colspan="2"><b><?=gettext("Radius (eap-radius)"); ?></b></td>
</tr>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("Backend for authentication");?> </td>
<td>
<select name="radius_source[]" class="selectpicker" id="user_source" multiple="multiple" size="3" <?=$legacy_radius_configured ? 'disabled=disabled' : ''?> >
<?php
$authmodes = explode(",", $pconfig['radius_source']);
foreach (auth_get_authserver_list() as $auth_key => $auth_server):
if ($auth_server['type'] == 'radius'):?>
<option value="<?=htmlspecialchars($auth_key)?>" <?=in_array($auth_key, $authmodes) ? 'selected="selected"' : ''?>><?=htmlspecialchars($auth_server['name'])?></option>
<?php
endif;
endforeach; ?>
</select>
<?php if ($legacy_radius_configured):?>
<i class="fa fa-info-circle" title="<?= html_safe(gettext('Disable or remove legacy mobile tunnel in order to use this.')) ?>" data-toggle="tooltip" ></i></a>
<?php endif;?>
</td>
</tr>
</table>
</div>
</section>
<section class="col-xs-12">
<div class="tab-content content-box col-xs-12">
<table class="table table-striped opnsense_standard_table_form" id="ike_extensions">
@ -493,120 +286,6 @@ foreach ($auth_servers as $auth_key => $auth_server) : ?>
</div>
</td>
</tr>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext('Network List') ?></td>
<td>
<input name="net_list" type="checkbox" id="net_list_enable" value="yes" <?= !empty($pconfig['net_list']) ? "checked=\"checked\"" : "";?> />
<?= gettext('Provide a list of accessible networks to clients') ?>
</td>
</tr>
<tr>
<td><a id="help_for_save_passwd" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Save Xauth Password"); ?></td>
<td>
<input name="save_passwd" type="checkbox" id="save_passwd_enable" value="yes" <?= !empty($pconfig['save_passwd']) ? "checked=\"checked\"" : "";?> />
<?= gettext('Allow clients to save Xauth passwords (Cisco VPN client only)') ?>
<div class="hidden" data-for="help_for_save_passwd">
<?=gettext("With iPhone clients, this does not work when deployed via the iPhone configuration utility, only by manual entry."); ?>
</div>
</td>
</tr>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("DNS Default Domain"); ?></td>
<td>
<input name="dns_domain_enable" type="checkbox" id="dns_domain_enable" value="yes" <?= !empty($pconfig['dns_domain']) ? "checked=\"checked\"" : "";?> onclick="dns_domain_change()" />
<?=gettext("Provide a default domain name to clients"); ?>
<input name="dns_domain" type="text" id="dns_domain" size="30" value="<?=$pconfig['dns_domain'];?>" />
</td>
</tr>
<tr>
<td><a id="help_for_dns_split_enable" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Split DNS"); ?></td>
<td>
<input name="dns_split_enable" type="checkbox" id="dns_split_enable" value="yes" <?= !empty($pconfig['dns_split']) ? "checked=\"checked\"" : "";?> onclick="dns_split_change()" />
<?= gettext('Provide a list of split DNS domain names to clients') ?>
<input name="dns_split" type="text" class="form-control" id="dns_split" size="30" value="<?=$pconfig['dns_split'];?>" />
<div class="hidden" data-for="help_for_dns_split_enable">
<?= gettext('Enter a comma-separated list. If left blank, and a default domain is set, it will be used for this value.') ?>
</div>
</td>
</tr>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?= gettext('DNS Servers') ?></td>
<td>
<input name="dns_server_enable" type="checkbox" id="dns_server_enable" value="yes" <?= !empty($pconfig['dns_server1']) || !empty($pconfig['dns_server2']) || !empty($pconfig['dns_server3']) || !empty($pconfig['dns_server4']) ? "checked=\"checked\"" : "";?> onclick="dns_server_change()" />
<?=gettext("Provide a DNS server list to clients"); ?>
<div id="dns_server_enable_inputs">
<?=gettext("Server"); ?> #1:
<input name="dns_server1" type="text" class="form-control" id="dns_server1" size="20" value="<?=$pconfig['dns_server1'];?>" />
<?=gettext("Server"); ?> #2:
<input name="dns_server2" type="text" class="form-control" id="dns_server2" size="20" value="<?=$pconfig['dns_server2'];?>" />
<?=gettext("Server"); ?> #3:
<input name="dns_server3" type="text" class="form-control" id="dns_server3" size="20" value="<?=$pconfig['dns_server3'];?>" />
<?=gettext("Server"); ?> #4:
<input name="dns_server4" type="text" class="form-control" id="dns_server4" size="20" value="<?=$pconfig['dns_server4'];?>" />
</div>
</td>
</tr>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("WINS Servers"); ?></td>
<td>
<input name="wins_server_enable" type="checkbox" id="wins_server_enable" value="yes" <?= !empty($pconfig['wins_server1']) || !empty($pconfig['wins_server2']) ? "checked=\"checked\"" : "";?> onclick="wins_server_change()" />
<?= gettext('Provide a WINS server list to clients') ?>
<div id="wins_server_enable_inputs">
<?=gettext("Server"); ?> #1:
<input name="wins_server1" type="text" class="form-control" id="wins_server1" size="20" value="<?=$pconfig['wins_server1'];?>" />
<?=gettext("Server"); ?> #2:
<input name="wins_server2" type="text" class="form-control" id="wins_server2" size="20" value="<?=$pconfig['wins_server2'];?>" />
</div>
</td>
</tr>
<tr>
<td><a id="help_for_pfs_group" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext('Phase 2 PFS Group') ?></td>
<td>
<select name="pfs_group" class="selectpicker" id="pfs_group">
<?php
$p2_dhgroups = array(
0 => gettext('off'),
1 => '1 (768 bits)',
2 => '2 (1024 bits)',
5 => '5 (1536 bits)',
14 => '14 (2048 bits)',
15 => '15 (3072 bits)',
16 => '16 (4096 bits)',
17 => '17 (6144 bits)',
18 => '18 (8192 bits)',
19 => '19 (NIST EC 256 bits)',
20 => '20 (NIST EC 384 bits)',
21 => '21 (NIST EC 521 bits)',
22 => '22 (1024(sub 160) bits)',
23 => '23 (2048(sub 224) bits)',
24 => '24 (2048(sub 256) bits)',
28 => '28 (Brainpool EC 256 bits)',
29 => '29 (Brainpool EC 384 bits)',
30 => '30 (Brainpool EC 512 bits)',
31 => '31 (Elliptic Curve 25519)',
);
foreach ($p2_dhgroups as $keygroup => $keygroupname): ?>
<option value="<?=$keygroup;
?>" <?= $pconfig['pfs_group'] == $keygroup ? "selected=\"selected\"" : "" ; ?>>
<?=$keygroupname;?>
</option>
<?php
endforeach;
?>
</select>
<div class="hidden" data-for="help_for_pfs_group">
<?=gettext("Provide the selected phase 2 PFS group to all mobile clients."); ?>
</div>
</td>
</tr>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?= gettext('Login Banner') ?></td>
<td>
<input name="login_banner_enable" type="checkbox" id="login_banner_enable" value="yes" <?= !empty($pconfig['login_banner']) ? "checked=\"checked\"" : "";?> onclick="login_banner_change()" />
<?=gettext("Provide a login banner to clients"); ?>
<textarea name="login_banner" cols="65" rows="7" id="login_banner" class="formpre"><?=$pconfig['login_banner'];?></textarea>
</td>
</tr>
</table>
</div>
</section>