security/strongswan-legacy: plugin done

PR: https://github.com/opnsense/core/issues/8348
This commit is contained in:
Franco Fichtner 2025-07-19 15:47:31 +02:00
parent e4f3b8f873
commit fd49d06c60
7 changed files with 2548 additions and 3 deletions

View File

@ -20,7 +20,7 @@ Copyright (c) 2020 devNan0 <nan0@nan0.dev>
Copyright (c) 2023 Dmitry Shinkaruk
Copyright (c) 2024 DollarSign23
Copyright (c) 2006 Eric Friesen
Copyright (c) 2008-2010 Ermal Luçi
Copyright (c) 2008-2014 Ermal Luçi
Copyright (c) 2016-2019 EURO-LOG AG
Copyright (c) 2017-2020 Fabian Franz
Copyright (c) 2019 Felix Matouschek <felix@matouschek.org>
@ -42,7 +42,7 @@ Copyright (c) 2024 laraveluser
Copyright (c) 2023 Liam Steckler <liam@liamsteckler.com>
Copyright (c) 2020-2021 Manuel Faux
Copyright (c) 2021 Manuel Hofmann
Copyright (c) 2003-2004 Manuel Kasper <mk@neon1.net>
Copyright (c) 2003-2005 Manuel Kasper <mk@neon1.net>
Copyright (c) 2023 Marc Bartelt
Copyright (c) 2021 Marcel Koepfli
Copyright (c) 2021 Markus Peter <mpeter@one-it.de>
@ -63,6 +63,7 @@ Copyright (c) 2022 Nikolaj Brinch Jørgensen
Copyright (c) 2021 Nim G
Copyright (c) 2023 Oliver Hartl
Copyright (c) 2024 Olly Baker <ilumos@gmail.com>
Copyright (c) 2019 Pascal Mathis <mail@pascalmathis.com>
Copyright (c) 2025 Ralph Moser, PJ Monitoring GmbH
Copyright (c) 2024 realizelol
Copyright (c) 2022 Robbert Rijkse

View File

@ -1,5 +1,5 @@
PLUGIN_NAME= strongswan-legacy
PLUGIN_VERSION= 0.1
PLUGIN_VERSION= 1.0
PLUGIN_COMMENT= IPsec legacy support
PLUGIN_DEPENDS= # strongswan
PLUGIN_MAINTAINER= ad@opnsense.org

View File

@ -0,0 +1,28 @@
<acl>
<page-vpn-ipsec>
<name>VPN: IPsec: Tunnels [legacy]</name>
<patterns>
<pattern>ui/ipsec/tunnels</pattern>
<pattern>api/ipsec/tunnel/*</pattern>
<pattern>api/ipsec/legacy_subsystem/*</pattern>
</patterns>
</page-vpn-ipsec>
<page-vpn-ipsec-editphase1>
<name>VPN: IPsec: Edit Phase 1</name>
<patterns>
<pattern>vpn_ipsec_phase1.php*</pattern>
</patterns>
</page-vpn-ipsec-editphase1>
<page-vpn-ipsec-editphase2>
<name>VPN: IPsec: Edit Phase 2</name>
<patterns>
<pattern>vpn_ipsec_phase2.php*</pattern>
</patterns>
</page-vpn-ipsec-editphase2>
<page-vpn-ipsec-mobile>
<name>VPN: IPsec: Mobile [legacy]</name>
<patterns>
<pattern>vpn_ipsec_mobile.php*</pattern>
</patterns>
</page-vpn-ipsec-mobile>
</acl>

View File

@ -0,0 +1,13 @@
<menu>
<VPN>
<IPsec>
<Tunnels order="10" VisibleName="Tunnel Settings [legacy]" url="/ui/ipsec/tunnels">
<Phase1 url="/vpn_ipsec_phase1.php*" visibility="hidden"/>
<Phase2 url="/vpn_ipsec_phase2.php*" visibility="hidden"/>
</Tunnels>
<Mobile order="20" VisibleName="Mobile Clients [legacy]" url="/vpn_ipsec_mobile.php">
<Act url="/vpn_ipsec_mobile.php*" visibility="hidden"/>
</Mobile>
</IPsec>
</VPN>
</menu>

View File

@ -0,0 +1,310 @@
<?php
/*
* Copyright (C) 2014-2025 Deciso B.V.
* Copyright (C) 2008 Shrew Soft Inc. <mgrooms@shrew.net>
* 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.
*/
require_once("guiconfig.inc");
require_once("interfaces.inc");
require_once("filter.inc");
require_once("system.inc");
require_once("plugins.inc.d/ipsec.inc");
config_read_array('ipsec', 'client');
config_read_array('ipsec', 'phase1');
// define formfields
$form_fields = "pool_address,pool_netbits,pool_address_v6,pool_netbits_v6";
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// pass savemessage
if (isset($_GET['savemsg'])) {
$savemsg = htmlspecialchars($_GET['savemsg']);
}
$pconfig = array();
// defaults
$pconfig['pool_netbits'] = 24;
$pconfig['pool_netbits_v6'] = 64;
// copy / initialize $pconfig attributes
foreach (explode(",", $form_fields) as $fieldname) {
$fieldname = trim($fieldname);
if (isset($config['ipsec']['client'][$fieldname])) {
$pconfig[$fieldname] = $config['ipsec']['client'][$fieldname];
} elseif (!isset($pconfig[$fieldname])) {
// initialize element
$pconfig[$fieldname] = null;
}
}
if (isset($config['ipsec']['client']['enable'])) {
$pconfig['enable'] = true;
}
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input_errors = array();
$pconfig = $_POST;
if (isset($_POST['create'])) {
// create new phase1 entry
header(url_safe('Location: /vpn_ipsec_phase1.php?mobile=true'));
exit;
} elseif (isset($_POST['apply'])) {
// apply changes
ipsec_configure_do();
$savemsg = get_std_save_message(true);
clear_subsystem_dirty('ipsec');
header(url_safe('Location: /vpn_ipsec_mobile.php?savemsg=%s', array($savemsg)));
exit;
} elseif (isset($_POST['submit'])) {
// save form changes
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.");
}
if (!empty($pconfig['pool_address_v6']) && !is_ipaddr($pconfig['pool_address_v6'])) {
$input_errors[] = gettext("A valid IPv6 address for 'Virtual IPv6 Address Pool Network' must be specified.");
}
if (count($input_errors) == 0) {
$client = array();
$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])) {
$client[$fieldname] = $pconfig[$fieldname];
}
}
if (!empty($pconfig['enable'])) {
$client['enable'] = true;
}
$config['ipsec']['client'] = $client;
write_config();
mark_subsystem_dirty('ipsec');
header(url_safe('Location: /vpn_ipsec_mobile.php'));
exit;
}
}
// initialize missing post attributes
foreach (explode(",", $form_fields) as $fieldname) {
$fieldname = trim($fieldname);
if (!isset($pconfig[$fieldname])) {
$pconfig[$fieldname] = null;
}
}
}
legacy_html_escape_form_data($pconfig);
$service_hook = 'strongswan';
include("head.inc");
?>
<body>
<script>
//<![CDATA[
$( document ).ready(function() {
pool_change();
pool_v6_change();
$("#ike_mobile_enable").change(function(){
if ($(this).is(':checked')) {
$("#ike_extensions").find("tr:not(.ike_heading)").show();
} else {
$("#ike_extensions").find("tr:not(.ike_heading)").hide();
}
});
$("#ike_mobile_enable").change();
});
function pool_change() {
if (document.iform.pool_enable.checked) {
document.iform.pool_address.disabled = 0;
document.iform.pool_netbits.disabled = 0;
} else {
document.iform.pool_address.disabled = 1;
document.iform.pool_netbits.disabled = 1;
}
}
function pool_v6_change() {
if (document.iform.pool_enable_v6.checked) {
document.iform.pool_address_v6.disabled = 0;
document.iform.pool_netbits_v6.disabled = 0;
} else {
document.iform.pool_address_v6.disabled = 1;
document.iform.pool_netbits_v6.disabled = 1;
}
}
//]]>
</script>
<?php include("fbegin.inc"); ?>
<section class="page-content-main">
<div class="container-fluid">
<div class="row">
<?php
if (isset($savemsg)) {
print_info_box($savemsg);
}
if (isset($config['ipsec']['enable']) && is_subsystem_dirty('ipsec')) {
print_info_box_apply(gettext("The IPsec tunnel configuration has been changed") . ".<br />" . gettext("You must apply the changes in order for them to take effect."));
}
$ph1found = false;
$legacy_radius_configured = false;
foreach ($config['ipsec']['phase1'] as $ph1ent) {
if (!isset($ph1ent['disabled']) && isset($ph1ent['mobile'])) {
$ph1found = true;
if (($ph1ent['authentication_method'] ?? '') == 'eap-radius') {
$legacy_radius_configured = true;
}
}
}
function print_legacy_box($msg, $name, $value)
{
$savebutton = "<form method=\"post\">";
$savebutton .= "<input name=\"{$name}\" type=\"submit\" class=\"btn btn-default\" id=\"{$name}\" value=\"{$value}\" />";
if (!empty($_POST['if'])) {
$savebutton .= "<input type=\"hidden\" name=\"if\" value=\"" . htmlspecialchars($_POST['if']) . "\" />";
}
$savebutton .= '</form>';
echo <<<EOFnp
<div class="col-xs-12">
<div class="alert alert-info alert-dismissible" role="alert">
{$savebutton}
<p>{$msg}</p>
</div>
</div>
EOFnp;
}
if (!empty($pconfig['enable']) && !$ph1found && !(new OPNsense\IPsec\Swanctl())->isEnabled()) {
print_legacy_box(gettext("Support for IPsec Mobile clients is enabled but a Phase1 definition was not found") . ".<br />" . gettext("When using (legacy) tunnels, please click Create to define one."), "create", gettext("Create Phase1"));
}
if (isset($input_errors) && count($input_errors) > 0) {
print_input_errors($input_errors);
}
?>
<form method="post" name="iform" id="iform">
<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">
<tr class="ike_heading">
<td style="width:22%"><b><?=gettext("IKE Extensions"); ?> </b></td>
<td style="width:78%; text-align:right">
<small><?=gettext("full help"); ?> </small>
<i class="fa fa-toggle-off text-danger" style="cursor: pointer;" id="show_all_help_page"></i>
</td>
</tr>
<tr class="ike_heading">
<td> <a id="help_for_enable" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Enable")?></td>
<td>
<input name="enable" id="ike_mobile_enable" type="checkbox" value="yes" <?= !empty($pconfig['enable']) ? "checked=\"checked\"" : "";?> />
<?=gettext("Enable IPsec Mobile Client Support"); ?>
<div class="hidden" data-for="help_for_enable">
<?= gettext(
'Enable mobile settings, '.
'some of the settings below depend on configuration choices in configured tunnels, ' .
'when not dependent on configured networks, they will also be used for configured connections when this option is checked.') ?>
</div>
</td>
</tr>
<tr>
<td colspan="2"><b><?=gettext("Client Configuration (mode-cfg)"); ?> </b></td>
</tr>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("Virtual IPv4 Address Pool"); ?></td>
<td>
<input name="pool_enable" type="checkbox" id="pool_enable" value="yes" <?= !empty($pconfig['pool_address'])&&!empty($pconfig['pool_netbits']) ? "checked=\"checked\"" : "";?> onclick="pool_change()" />
<?=gettext("Provide a virtual IPv4 address to clients"); ?>
<div class="input-group">
<input name="pool_address" type="text" class="form-control" id="pool_address" size="20" value="<?=$pconfig['pool_address'];?>" style="width:200px;" />
<select name="pool_netbits" class="selectpicker form-control" id="pool_netbits" data-width="70px" data-size="10">
<?php
for ($i = 32; $i >= 0; $i--) :?>
<option value="<?=$i;?>" <?= ($i == $pconfig['pool_netbits']) ? "selected=\"selected\"" : "";?>>
<?=$i;?>
</option>
<?php
endfor; ?>
</select>
</div>
</td>
</tr>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("Virtual IPv6 Address Pool"); ?></td>
<td>
<input name="pool_enable_v6" type="checkbox" id="pool_enable_v6" value="yes" <?= !empty($pconfig['pool_address_v6'])&&!empty($pconfig['pool_netbits_v6']) ? "checked=\"checked\"" : "";?> onclick="pool_v6_change()" />
<?=gettext("Provide a virtual IPv6 address to clients"); ?>
<div class="input-group">
<input name="pool_address_v6" type="text" class="form-control" id="pool_address_v6" size="20" value="<?=$pconfig['pool_address_v6'];?>" style="width:200px;" />
<select name="pool_netbits_v6" class="selectpicker form-control" id="pool_netbits_v6" data-width="70px" data-size="10">
<?php
for ($i = 128; $i >= 0; $i--) :?>
<option value="<?=$i;?>" <?= ($i == $pconfig['pool_netbits_v6']) ? "selected=\"selected\"" : "";?>>
<?=$i;?>
</option>
<?php
endfor; ?>
</select>
</div>
</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">
<tr>
<td style="width:22%">&nbsp;</td>
<td style="width:78%;">
<input name="submit" type="submit" class="btn btn-primary" value="<?=html_safe(gettext('Save')); ?>" />
</td>
</tr>
</table>
</div>
</div>
</section>
</form>
</div>
</div>
</section>
<?php include("foot.inc"); ?>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,842 @@
<?php
/*
* Copyright (C) 2014 Deciso B.V.
* Copyright (C) 2008 Shrew Soft Inc. <mgrooms@shrew.net>
* Copyright (C) 2003-2005 Manuel Kasper <mk@neon1.net>
* 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.
*/
require_once("guiconfig.inc");
require_once("interfaces.inc");
require_once("plugins.inc.d/ipsec.inc");
/**
* combine ealgos and keylen_* tags
*/
function pconfig_to_ealgos($pconfig)
{
$ealgos = [];
if (isset($pconfig['ealgos'])) {
foreach (ipsec_p2_ealgos() as $algo_name => $algo_data) {
if (in_array($algo_name, $pconfig['ealgos'])) {
$ealgos[] = ['name' => $algo_name];
}
}
}
return $ealgos;
}
function ealgos_to_pconfig(& $ealgos, & $pconfig)
{
$p2_ealgos = ipsec_p2_ealgos();
$pconfig['ealgos'] = [];
foreach ($ealgos as $cnf_algo_data) {
foreach ($p2_ealgos as $algo_name => $algo_data) {
if ($algo_name == $cnf_algo_data['name']) {
$pconfig['ealgos'][] = $algo_name;
} elseif ($algo_data['name'] == $cnf_algo_data['name']) {
// XXX: extract and convert legacy encryption-algorithm-option setting
if ($cnf_algo_data['keylen'] == $algo_data['keylen'] || $cnf_algo_data['keylen'] == "auto") {
$pconfig['ealgos'][] = $algo_name;
}
}
}
}
return $ealgos;
}
/**
* convert <tag>id_address, <tag>id_netbits, <tag>id_type
* to type/address/netbits structure
*/
function pconfig_to_idinfo($prefix, $pconfig)
{
$type = isset($pconfig[$prefix."id_type"]) ? $pconfig[$prefix."id_type"] : null;
$address = isset($pconfig[$prefix."id_address"]) ? $pconfig[$prefix."id_address"] : null;
$netbits = isset($pconfig[$prefix."id_netbits"]) ? $pconfig[$prefix."id_netbits"] : null;
switch ($type) {
case "address":
return array('type' => $type, 'address' => $address);
case "network":
return array('type' => $type, 'address' => $address, 'netbits' => $netbits);
default:
return array('type' => $type );
}
}
/**
* reverse pconfig_to_idinfo from $idinfo array to $pconfig
*/
function idinfo_to_pconfig($prefix, $idinfo, & $pconfig)
{
switch ($idinfo['type']) {
case "address":
$pconfig[$prefix."id_type"] = $idinfo['type'];
$pconfig[$prefix."id_address"] = $idinfo['address'];
break;
case "network":
$pconfig[$prefix."id_type"] = $idinfo['type'];
$pconfig[$prefix."id_address"] = $idinfo['address'];
$pconfig[$prefix."id_netbits"] = $idinfo['netbits'];
break;
default:
$pconfig[$prefix."id_type"] = $idinfo['type'];
break;
}
}
/**
* search phase 2 entries for record with uniqid
*/
function getIndexByUniqueId($uniqid)
{
global $config;
$p2index = null;
if ($uniqid != null) {
foreach ($config['ipsec']['phase2'] as $idx => $ph2) {
if ($ph2['uniqid'] == $uniqid) {
$p2index = $idx;
break;
}
}
}
return $p2index;
}
config_read_array('ipsec', 'client');
config_read_array('ipsec', 'phase2');
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// lookup p2index
if (!empty($_GET['dup'])) {
$p2index = getIndexByUniqueId($_GET['dup']);
} elseif (!empty($_GET['p2index'])) {
$p2index = getIndexByUniqueId($_GET['p2index']);
} else {
$p2index = null;
}
// initialize form data
$pconfig = array();
$phase2_fields = "ikeid,mode,descr,uniqid,proto,hash-algorithm-option,pfsgroup,lifetime,pinghost,protocol,spd,";
$phase2_fields .= "tunnel_local,tunnel_remote";
if ($p2index !== null) {
// 1-on-1 copy
foreach (explode(",", $phase2_fields) as $fieldname) {
$fieldname = trim($fieldname);
if (isset($config['ipsec']['phase2'][$p2index][$fieldname])) {
$pconfig[$fieldname] = $config['ipsec']['phase2'][$p2index][$fieldname];
} elseif (!isset($pconfig[$fieldname])) {
// initialize element
$pconfig[$fieldname] = null;
}
}
// fields with some kind of logic
$pconfig['disabled'] = isset($config['ipsec']['phase2'][$p2index]['disabled']);
idinfo_to_pconfig("local", $config['ipsec']['phase2'][$p2index]['localid'], $pconfig);
idinfo_to_pconfig("remote", $config['ipsec']['phase2'][$p2index]['remoteid'], $pconfig);
if (!empty($config['ipsec']['phase2'][$p2index]['encryption-algorithm-option'])) {
ealgos_to_pconfig($config['ipsec']['phase2'][$p2index]['encryption-algorithm-option'], $pconfig);
} else {
$pconfig['ealgos'] = [];
}
if (!empty($_GET['dup'])) {
$pconfig['uniqid'] = uniqid();
}
} else {
if (isset($_GET['ikeid'])) {
$pconfig['ikeid'] = $_GET['ikeid'];
}
/* defaults */
$pconfig['localid_type'] = "lan";
$pconfig['remoteid_type'] = "network";
$pconfig['protocol'] = "esp";
$pconfig['ealgos'] = ['aes256gcm16'];
$pconfig['hash-algorithm-option'] = ['hmac_sha256'];
$pconfig['pfsgroup'] = "0";
$pconfig['uniqid'] = uniqid();
// init empty
foreach (explode(",", $phase2_fields) as $fieldname) {
$fieldname = trim($fieldname);
if (!isset($pconfig[$fieldname])) {
$pconfig[$fieldname] = null;
}
}
}
/* mobile client */
foreach ($config['ipsec']['phase1'] as $phase1ent) {
if ($phase1ent['ikeid'] == $pconfig['ikeid'] && isset($phase1ent['mobile'])) {
$pconfig['mobile'] = true;
break;
}
}
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!empty($_POST['uniqid'])) {
$p2index = getIndexByUniqueId($_POST['uniqid']);
} else {
$p2index = null;
}
$input_errors = array();
$pconfig = $_POST;
/* input validation */
if (!isset($_POST['ikeid'])) {
$input_errors[] = gettext("A valid ikeid must be specified.");
}
$reqdfields = explode(" ", "localid_type uniqid");
$reqdfieldsn = array(gettext("Local network type"), gettext("Unique Identifier"));
if (!isset($pconfig['mobile'])) {
$reqdfields[] = "remoteid_type";
$reqdfieldsn[] = gettext("Remote network type");
}
do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
if (($pconfig['mode'] == 'tunnel') || ($pconfig['mode'] == 'tunnel6')) {
switch ($pconfig['localid_type']) {
case 'network':
if (($pconfig['localid_netbits'] != 0 && !$pconfig['localid_netbits']) || !is_numeric($pconfig['localid_netbits'])) {
$input_errors[] = gettext('A valid local network bit count must be specified.');
}
/* FALLTHROUGH */
case 'address':
if (!$pconfig['localid_address'] || !is_ipaddr($pconfig['localid_address'])) {
$input_errors[] = gettext('A valid local network IP address must be specified.');
} elseif (is_ipaddrv4($pconfig['localid_address']) && ($pconfig['mode'] != 'tunnel')) {
$input_errors[] = gettext('A valid local network IPv4 address must be specified or you need to change Mode to IPv6');
} elseif (is_ipaddrv6($pconfig['localid_address']) && ($pconfig['mode'] != 'tunnel6')) {
$input_errors[] = gettext('A valid local network IPv6 address must be specified or you need to change Mode to IPv4');
}
break;
default:
if ($pconfig['mode'] == 'tunnel') {
list (, $subnet) = interfaces_primary_address($pconfig['localid_type']);
if (!is_subnetv4($subnet)) {
$input_errors[] = sprintf(
gettext('Invalid local network: %s has no valid IPv4 network.'),
convert_friendly_interface_to_friendly_descr($pconfig['localid_type'])
);
}
} elseif ($pconfig['mode'] == 'tunnel6') {
list (, $subnet) = interfaces_primary_address6($pconfig['localid_type']);
if (!is_subnetv6($subnet)) {
$input_errors[] = sprintf(
gettext('Invalid local network: %s has no valid IPv6 network.'),
convert_friendly_interface_to_friendly_descr($pconfig['localid_type'])
);
}
}
break;
}
switch ($pconfig['remoteid_type']) {
case "network":
if (($pconfig['remoteid_netbits'] != 0 && !$pconfig['remoteid_netbits']) || !is_numeric($pconfig['remoteid_netbits'])) {
$input_errors[] = gettext("A valid remote network bit count must be specified.");
}
// address rules also apply to network type (hence, no break)
case "address":
if (!$pconfig['remoteid_address'] || !is_ipaddr($pconfig['remoteid_address'])) {
$input_errors[] = gettext("A valid remote network IP address must be specified.");
} elseif (is_ipaddrv4($pconfig['remoteid_address']) && ($pconfig['mode'] != "tunnel")) {
$input_errors[] = gettext("A valid remote network IPv4 address must be specified or you need to change Mode to IPv6");
} elseif (is_ipaddrv6($pconfig['remoteid_address']) && ($pconfig['mode'] != "tunnel6")) {
$input_errors[] = gettext("A valid remote network IPv6 address must be specified or you need to change Mode to IPv4");
}
break;
}
} elseif ($pconfig['mode'] == 'route-based') {
// validate if both tunnel networks are using the correct address family
if (!is_ipaddr($pconfig['tunnel_local']) || !is_ipaddr($pconfig['tunnel_remote'])) {
if (!is_ipaddr($pconfig['tunnel_local'])) {
$input_errors[] = gettext('A valid local network IP address must be specified.');
}
if (!is_ipaddr($pconfig['tunnel_remote'])) {
$input_errors[] = gettext("A valid remote network IP address must be specified.");
}
} elseif(
!(is_ipaddrv4($pconfig['tunnel_local']) && is_ipaddrv4($pconfig['tunnel_remote'])) &&
!(is_ipaddrv6($pconfig['tunnel_local']) && is_ipaddrv6($pconfig['tunnel_remote']))
) {
$input_errors[] = gettext("A valid local network IP address must be specified.");
$input_errors[] = gettext("A valid remote network IP address must be specified.");
}
}
/* Validate enabled phase2's are not duplicates */
if (isset($pconfig['mobile'])) {
/* User is adding phase 2 for mobile phase1 */
foreach ($config['ipsec']['phase2'] as $key => $name) {
if (isset($name['mobile']) && $pconfig['ikeid'] == $name['ikeid'] && $name['uniqid'] != $pconfig['uniqid']) {
/* check duplicate localids only for mobile clients */
$localid_data = ipsec_idinfo_to_cidr($name['localid'], false, $name['mode']);
$entered = array();
$entered['type'] = $pconfig['localid_type'];
if (isset($pconfig['localid_address'])) {
$entered['address'] = $pconfig['localid_address'];
}
if (isset($pconfig['localid_netbits'])) {
$entered['netbits'] = $pconfig['localid_netbits'];
}
$entered_localid_data = ipsec_idinfo_to_cidr($entered, false, $pconfig['mode']);
if ($localid_data == $entered_localid_data) {
/* adding new p2 entry */
$input_errors[] = gettext("Phase2 with this Local Network is already defined for mobile clients.");
break;
}
}
}
} else {
/* User is adding phase 2 for site-to-site phase1 */
foreach ($config['ipsec']['phase2'] as $key => $name) {
if (!isset($name['mobile']) && $pconfig['mode'] != 'route-based' &&
$pconfig['ikeid'] == $name['ikeid'] && $pconfig['uniqid'] != $name['uniqid']) {
/* check duplicate subnets only for given phase1 */
$localid_data = ipsec_idinfo_to_cidr($name['localid'], false, $name['mode']);
$remoteid_data = ipsec_idinfo_to_cidr($name['remoteid'], false, $name['mode']);
$entered_local = array();
$entered_local['type'] = $pconfig['localid_type'];
if (isset($pconfig['localid_address'])) {
$entered_local['address'] = $pconfig['localid_address'];
}
if (isset($pconfig['localid_netbits'])) {
$entered_local['netbits'] = $pconfig['localid_netbits'];
}
$entered_localid_data = ipsec_idinfo_to_cidr($entered_local, false, $pconfig['mode']);
$entered_remote = array();
$entered_remote['type'] = $pconfig['remoteid_type'];
if (isset($pconfig['remoteid_address'])) {
$entered_remote['address'] = $pconfig['remoteid_address'];
}
if (isset($pconfig['remoteid_netbits'])) {
$entered_remote['netbits'] = $pconfig['remoteid_netbits'];
}
$entered_remoteid_data = ipsec_idinfo_to_cidr($entered_remote, false, $pconfig['mode']);
if ($localid_data == $entered_localid_data && $remoteid_data == $entered_remoteid_data) {
/* adding new p2 entry */
$input_errors[] = gettext("Phase2 with this Local/Remote networks combination is already defined for this Phase1.");
break;
}
}
}
}
if (!empty($pconfig['ikeid'])) {
foreach ($config['ipsec']['phase1'] as $phase1ent) {
if ($phase1ent['ikeid'] == $pconfig['ikeid'] &&
$pconfig['mode'] == 'route-based' &&
empty($phase1ent['noinstallpolicy'])
) {
$input_errors[] = gettext(
"Install policy on phase1 is not a valid option when using Route-based phase 2 entries."
);
break;
}
}
}
/* For ESP protocol, handle encryption algorithms */
if ($pconfig['protocol'] == "esp") {
$ealgos = pconfig_to_ealgos($pconfig);
if (!count($ealgos)) {
$input_errors[] = gettext("At least one encryption algorithm must be selected.");
} else {
if (empty($pconfig['hash-algorithm-option'])) {
foreach ($ealgos as $ealgo) {
if (!strpos($ealgo['name'], "gcm")) {
$input_errors[] = gettext("At least one hashing algorithm needs to be selected.");
break;
}
}
$pconfig['hash-algorithm-option'] = array();
}
}
}
if ((!empty($_POST['lifetime']) && !is_numeric($_POST['lifetime']))) {
$input_errors[] = gettext("The P2 lifetime must be an integer.");
}
if (!empty($pconfig['spd'])) {
foreach (explode(',', $pconfig['spd']) as $spd_entry) {
if (($pconfig['mode'] == "tunnel" && !is_subnetv4(trim($spd_entry))) ||
($pconfig['mode'] == "tunnel6" && !is_subnetv6(trim($spd_entry)))) {
$input_errors[] = sprintf(gettext('SPD "%s" is not a valid network, it should match the tunnel type (IPv4/IPv6).'), $spd_entry) ;
}
}
}
if (count($input_errors) == 0) {
$ph2ent = array();
$copy_fields = "ikeid,uniqid,mode,pfsgroup,lifetime,pinghost,descr,protocol,spd";
// 1-on-1 copy
foreach (explode(",", $copy_fields) as $fieldname) {
$fieldname = trim($fieldname);
if (!empty($pconfig[$fieldname])) {
$ph2ent[$fieldname] = $pconfig[$fieldname];
}
}
// fields with some logic in them
$ph2ent['disabled'] = $pconfig['disabled'] ? true : false;
if (($ph2ent['mode'] == "tunnel") || ($ph2ent['mode'] == "tunnel6")) {
$ph2ent['localid'] = pconfig_to_idinfo("local", $pconfig);
$ph2ent['remoteid'] = pconfig_to_idinfo("remote", $pconfig);
} elseif ($ph2ent['mode'] == 'route-based') {
$ph2ent['tunnel_local'] = $pconfig['tunnel_local'];
$ph2ent['tunnel_remote'] = $pconfig['tunnel_remote'];
}
$ph2ent['encryption-algorithm-option'] = pconfig_to_ealgos($pconfig);
if (!empty($pconfig['hash-algorithm-option'])) {
$ph2ent['hash-algorithm-option'] = $pconfig['hash-algorithm-option'];
} else {
unset($ph2ent['hash-algorithm-option']);
}
// attach or generate reqid
if ($p2index !== null && !empty($config['ipsec']['phase2'][$p2index]['reqid'])) {
$ph2ent['reqid'] = $config['ipsec']['phase2'][$p2index]['reqid'];
} else {
$reqids = [];
foreach ($config['ipsec']['phase2'] as $tmp) {
if (!empty($tmp['reqid'])) {
$reqids[] = $tmp['reqid'];
}
}
for ($i=1; $i < 65535; $i++) {
if (!in_array($i, $reqids)) {
$ph2ent['reqid'] = $i;
break;
}
}
}
// save to config
if ($p2index !== null) {
$config['ipsec']['phase2'][$p2index] = $ph2ent;
} else {
$config['ipsec']['phase2'][] = $ph2ent;
}
write_config();
mark_subsystem_dirty('ipsec');
header(url_safe('Location: /ui/ipsec/tunnels'));
exit;
}
}
$service_hook = 'strongswan';
legacy_html_escape_form_data($pconfig);
include("head.inc");
?>
<body>
<?php include("fbegin.inc"); ?>
<script>
$( document ).ready(function() {
$("#mode").change(function(){
$(".opt_localid").hide();
$(".opt_remoteid").hide();
$(".opt_route").hide();
if ($(this).val() == 'tunnel' || $(this).val() == 'tunnel6') {
$(".opt_localid").show();
if ($("#mobile").val() == undefined) {
$(".opt_remoteid").show();
}
} else if ($(this).val() == 'route-based') {
$(".opt_route").show();
}
$(window).resize();
});
$("#mode").change();
$("#proto").change(function(){
if ($(this).val() == 'esp') {
$("#opt_enc").show();
} else {
$("#opt_enc").hide();
}
$(window).resize();
});
$("#proto").change();
['localid', 'remoteid'].map(function(field){
$("#"+field+"_type").change(function(){
$("#"+field+"_netbits").prop("disabled", true);
$("#"+field+"_address").prop("disabled", true);
switch ($(this).val()) {
case 'address':
$("#"+field+"_address").prop("disabled", false);
break;
case 'network':
$("#"+field+"_netbits").prop("disabled", false);
$("#"+field+"_address").prop("disabled", false);
break;
default:
break;
}
$(window).resize();
});
$("#"+field+"_type").change();
});
// hook in, ipv4/ipv6 selector events
hook_ipv4v6('ipv4v6net', 'network-id');
});
</script>
<?php
if (isset($input_errors) && count($input_errors) > 0) {
print_input_errors($input_errors);
}
?>
<section class="page-content-main">
<div class="container-fluid">
<div class="row">
<section class="col-xs-12">
<div class="tab-content content-box col-xs-12">
<form method="post" name="iform" id="iform">
<div class="table-responsive">
<table class="table table-striped opnsense_standard_table_form">
<tr>
<td style="width:22%"><b><?=gettext("General information"); ?></b></td>
<td style="width:78%; text-align:right">
<small><?=gettext("full help"); ?> </small>
<i class="fa fa-toggle-off text-danger" style="cursor: pointer;" id="show_all_help_page"></i>
</td>
</tr>
<tr>
<td style="width:22%"><a id="help_for_disabled" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Disabled"); ?></td>
<td style="width:78%" class="vtable">
<input name="disabled" type="checkbox" id="disabled" value="yes" <?= !empty($pconfig['disabled']) ? "checked=\"checked\"" : "" ;?> />
<div class="hidden" data-for="help_for_disabled">
<?=gettext("Disable this phase2 entry"); ?><br/>
<?=gettext("Set this option to disable this phase2 entry without " .
"removing it from the list"); ?>.
</div>
</td>
</tr>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("Mode"); ?></td>
<td>
<select name="mode" id="mode">
<?php
$p2_modes = array(
'tunnel' => 'Tunnel IPv4',
'tunnel6' => 'Tunnel IPv6',
'route-based' => 'Route-based',
'transport' => 'Transport');
foreach ($p2_modes as $name => $value) :
?>
<option value="<?=$name;?>"
<?=$name == $pconfig['mode'] ? "selected=\"selected\"":"" ;?>><?=$value;?>
</option>
<?php
endforeach;
?>
</select>
</td>
</tr>
<tr>
<td><a id="help_for_descr" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Description"); ?></td>
<td>
<input name="descr" type="text" id="descr" size="40" value="<?=$pconfig['descr'];?>" />
<div class="hidden" data-for="help_for_descr">
<?=gettext("You may enter a description here for your reference (not parsed)."); ?>
</div>
</td>
</tr>
<!-- Route based tunnel -->
<tr class="opt_route">
<td colspan="2"><b><?=gettext("Tunnel network");?></b></td>
</tr>
<tr class="opt_route">
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("Local Address");?> </td>
<td>
<input name="tunnel_local" type="text" id="tunnel_local" size="28" value="<?=$pconfig['tunnel_local'];?>" />
</td>
</tr>
<tr class="opt_route">
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("Remote Address");?> </td>
<td>
<input name="tunnel_remote" type="text" id="tunnel_remote" size="28" value="<?=$pconfig['tunnel_remote'];?>" />
</td>
</tr>
<!-- Tunnel settings -->
<tr class="opt_localid">
<td colspan="2"><b><?=gettext("Local Network");?></b></td>
</tr>
<tr class="opt_localid">
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("Type"); ?> </td>
<td>
<select name="localid_type" id="localid_type">
<option value="address" <?=$pconfig['localid_type'] == "address" ? "selected=\"selected\"" : ""?> ><?=gettext("Address"); ?></option>
<option value="network" <?=$pconfig['localid_type'] == "network" ? "selected=\"selected\"" : ""?> ><?=gettext("Network"); ?></option>
<?php
$iflist = get_configured_interface_with_descr();
foreach ($iflist as $ifname => $ifdescr) :?>
<option value="<?=htmlspecialchars($ifname);?>" <?= $pconfig['localid_type'] == $ifname ? "selected=\"selected\"" : "" ;?> >
<?=sprintf(gettext("%s subnet"), htmlspecialchars($ifdescr)); ?>
</option>
<?php
endforeach;?>
</select>
</td>
</tr>
<tr class="opt_localid">
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("Address:");?>&nbsp;&nbsp;</td>
<td>
<table style="max-width: 348px">
<tr>
<td>
<input name="localid_address" type="text" style="width: 278px" id="localid_address" size="28" value="<?=$pconfig['localid_address'];?>" />
</td>
<td>
<select name="localid_netbits" data-network-id="localid_address" class="selectpicker ipv4v6net" data-size="10" data-width="70px" id="localid_netbits">
<?php for ($i = 128; $i >= 0; $i--) : ?>
<option value="<?=$i;?>" <?= isset($pconfig['localid_netbits']) && $i == $pconfig['localid_netbits'] ? "selected=\"selected\"" : "";?>>
<?=$i;?>
</option>
<?php endfor ?>
</select>
</td>
</tr>
</table>
</td>
</tr>
<?php if (!isset($pconfig['mobile'])): ?>
<tr class="opt_remoteid">
<td colspan="2"><b><?=gettext("Remote Network");?></b></td>
</tr>
<tr class="opt_remoteid">
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("Type"); ?>:&nbsp;&nbsp;</td>
<td>
<select name="remoteid_type" id="remoteid_type">
<option value="address" <?= $pconfig['remoteid_type'] == "address" ? "selected=\"selected\"" : "";?>>
<?=gettext("Address"); ?>
</option>
<option value="network" <?= $pconfig['remoteid_type'] == "network" ? "selected=\"selected\"" : "";?>>
<?=gettext("Network"); ?>
</option>
</select>
</td>
</tr>
<tr class="opt_remoteid">
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("Address"); ?>:&nbsp;&nbsp;</td>
<td>
<table style="max-width: 348px">
<tr>
<td>
<input name="remoteid_address" type="text" style="width: 278px" id="remoteid_address" size="28" value="<?=$pconfig['remoteid_address'];?>" />
</td>
<td>
<select name="remoteid_netbits" data-network-id="remoteid_address" class="selectpicker ipv4v6net" data-size="10" data-width="70px" id="remoteid_netbits">
<?php for ($i = 128; $i >= 0; $i--): ?>
<option value="<?=$i;?>" <?= isset($pconfig['remoteid_netbits']) && $i == $pconfig['remoteid_netbits'] ? "selected=\"selected\"" : "";?> >
<?=$i;?>
</option>
<?php endfor ?>
</select>
</td>
</tr>
</table>
</td>
</tr>
<?php
endif; ?>
<tr>
<td colspan="2">
<b><?=gettext("Phase 2 proposal (SA/Key Exchange)"); ?></b>
</td>
</tr>
<tr>
<td><a id="help_for_proto" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Protocol"); ?></td>
<td style="width:78%" class="vtable">
<select name="protocol" id="proto">
<?php
foreach (array('esp' => 'ESP','ah' => 'AH') as $proto => $protoname) :?>
<option value="<?=$proto;?>" <?= $proto == $pconfig['protocol'] ? "selected=\"selected\"" : "";?>>
<?=$protoname;?>
</option>
<?php
endforeach; ?>
</select>
<br />
<div class="hidden" data-for="help_for_proto">
<?=gettext("ESP is encryption, AH is authentication only"); ?>
</div>
</td>
</tr>
<tr id="opt_enc">
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("Encryption algorithms"); ?></td>
<td>
<select name="ealgos[]" class="selectpicker" multiple="multiple">
<?php
foreach (ipsec_p2_ealgos() as $algo => $algodata) :?>
<option value="<?=$algo;?>" <?= (is_array($pconfig['ealgos']) && in_array($algo, $pconfig['ealgos'])) ? 'selected="selected"' : '' ?> >
<?=$algodata['descr'];?>
</option>
<?php
endforeach;?>
</select>
</td>
</tr>
<tr>
<td><a id="help_for_hashalg" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Hash algorithms"); ?></td>
<td style="width:78%" class="vtable">
<select name="hash-algorithm-option[]" class="selectpicker" multiple="multiple">
<?php foreach (ipsec_p2_halgos() as $algo => $algoname): ?>
<option value="<?= html_safe($algo) ?>" <?= (is_array($pconfig['hash-algorithm-option']) && in_array($algo, $pconfig['hash-algorithm-option'])) ? 'selected="selected"' : '' ?>>
<?= html_safe($algoname) ?>
</option>
<?php endforeach ?>
</select>
<div class="hidden" data-for="help_for_hashalg">
<?=gettext("Note: For security reasons avoid the use of the SHA1 algorithm."); ?>
</div>
</td>
</tr>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("PFS key group"); ?></td>
<td>
<?php
if (!isset($pconfig['mobile']) || !isset($config['ipsec']['client']['pfs_group'])) :?>
<select name="pfsgroup">
<?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;?>" <?= $keygroup == $pconfig['pfsgroup'] ? "selected=\"selected\"" : "";?>>
<?=$keygroupname;?>
</option>
<?php
endforeach; ?>
</select>
<?php
else :?>
<select disabled="disabled">
<option selected="selected"><?=$p2_pfskeygroups[$config['ipsec']['client']['pfs_group']];?></option>
</select>
<input name="pfsgroup" type="hidden" value="<?=$pconfig['pfsgroup'];?>" />
<br />
<em><?=gettext("Set globally in mobile client options"); ?></em>
<?php
endif; ?>
</td>
</tr>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("Lifetime"); ?></td>
<td>
<input name="lifetime" type="text" id="lifetime" size="20" value="<?=$pconfig['lifetime'];?>" />
<?=gettext("seconds"); ?>
</td>
</tr>
<tr>
<td colspan="2">
<b><?=gettext("Advanced Options"); ?></b>
</td>
</tr>
<tr>
<td><a id="help_for_pinghost" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Automatically ping host"); ?></td>
<td>
<input name="pinghost" type="text" id="pinghost" size="28" value="<?=$pconfig['pinghost'];?>" />
<div class="hidden" data-for="help_for_pinghost">
<?=gettext("IP address"); ?>
</div>
</td>
</tr>
<?php
if (!isset($pconfig['mobile'])):?>
<tr class="opt_localid">
<td><a id="help_for_spd" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Manual SPD entries"); ?></td>
<td>
<input name="spd" type="text" id="spd" value="<?= $pconfig['spd'];?>" />
<div class="hidden" data-for="help_for_spd">
<strong><?=gettext("Register additional Security Policy Database entries"); ?></strong><br/>
<?=gettext("Strongswan automatically creates SPD policies for the networks defined in this phase2. ".
"If you need to allow other networks to use this ipsec tunnel, you can add them here as a comma-separated list.".
"When configured, you can use network address translation to push packets through this tunnel from these networks."); ?><br/>
<small><?=gettext("e.g. 192.168.1.0/24, 192.168.2.0/24"); ?></small>
</div>
</td>
</tr>
<?php
endif; ?>
<tr>
<td>&nbsp;</td>
<td style="width:78%">
<?php
if (isset($pconfig['mobile'])) :?>
<input name="mobile" type="hidden" value="true" />
<input name="remoteid_type" type="hidden" value="mobile" />
<?php
endif; ?>
<input name="Submit" type="submit" class="btn btn-primary" value="<?=html_safe(gettext('Save')); ?>" />
<input name="ikeid" type="hidden" value="<?=$pconfig['ikeid'];?>" />
<input name="uniqid" type="hidden" value="<?=$pconfig['uniqid'];?>" />
</td>
</tr>
</table>
</div>
</form>
</div>
</section>
</div>
</div>
</section>
<?php include("foot.inc"); ?>