Firewall: NAT: One-to-One - refactor to MVC, closes https://github.com/opnsense/core/issues/7250

This commit is contained in:
Ad Schellevis 2024-04-25 19:13:50 +02:00
parent 6f24db1a01
commit cd81bcc964
14 changed files with 439 additions and 907 deletions

View File

@ -256,12 +256,6 @@ function filter_configure_sync($verbose = false, $load_aliases = true)
}
}
if (!empty($config['nat']['onetoone'])) {
// register user 1:1 mappings
foreach ($config['nat']['onetoone'] as $rule) {
$fw->registerDNatRule(500, $rule);
}
}
if (!empty($config['nat']['rule'])) {
// register user forward rules
foreach ($config['nat']['rule'] as $rule) {

View File

@ -180,6 +180,9 @@ function pf_firewall($fw)
foreach ($mdlFilter->snatrules->rule->sortedBy(["sequence"]) as $key => $rule) {
$fw->registerSNatRule(50, $rule->serialize());
}
foreach ($mdlFilter->onetoone->rule->sortedBy(["sequence"]) as $key => $rule) {
$fw->registerDNatRule(500, $rule->serialize());
}
foreach ($mdlFilter->npt->rule->sortedBy(["sequence"]) as $key => $rule) {
$fw->registerNptRule(50, $rule->serialize());
}

View File

@ -0,0 +1,67 @@
<?php
/*
* Copyright (C) 2024 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\Firewall\Api;
class OneToOneController extends FilterBaseController
{
protected static $categorysource = "onetoone.rule";
public function searchRuleAction()
{
$category = $this->request->get('category');
$filter_funct = function ($record) use ($category) {
return empty($category) || array_intersect(explode(',', $record->categories), $category);
};
return $this->searchBase("onetoone.rule", null, "sequence", $filter_funct);
}
public function setRuleAction($uuid)
{
return $this->setBase("rule", "onetoone.rule", $uuid);
}
public function addRuleAction()
{
return $this->addBase("rule", "onetoone.rule");
}
public function getRuleAction($uuid = null)
{
return $this->getBase("rule", "onetoone.rule", $uuid);
}
public function delRuleAction($uuid)
{
return $this->delBase("onetoone.rule", $uuid);
}
public function toggleRuleAction($uuid, $enabled = null)
{
return $this->toggleBase("onetoone.rule", $uuid, $enabled);
}
}

View File

@ -0,0 +1,62 @@
<?php
/*
* Copyright (C) 2024 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\Firewall;
class OneToOneController extends \OPNsense\Base\IndexController
{
public function indexAction()
{
$this->view->pick('OPNsense/Firewall/filter');
$this->view->ruleController = "one_to_one";
$this->view->gridFields = [
[
'id' => 'enabled', 'formatter' => 'rowtoggle' ,'width' => '6em', 'heading' => gettext('Enabled')
],
[
'id' => 'sequence','width' => '9em', 'heading' => gettext('Sequence')
],
[
'id' => 'interface','width' => '9em', 'heading' => gettext('Interface')
],
[
'id' => 'target', 'heading' => gettext('External')
],
[
'id' => 'source_net', 'heading' => gettext('Internal')
],
[
'id' => 'destination_net', 'heading' => gettext('Destination')
],
[
'id' => 'description', 'heading' => gettext('Description')
]
];
$this->view->formDialogFilterRule = $this->getForm("dialogOneToOneRule");
}
}

View File

@ -0,0 +1,81 @@
<form>
<field>
<id>rule.enabled</id>
<label>enabled</label>
<type>checkbox</type>
<help>Enable this rule</help>
</field>
<field>
<id>rule.sequence</id>
<label>Sequence</label>
<type>text</type>
</field>
<field>
<id>rule.log</id>
<label>Log</label>
<type>checkbox</type>
<help>Log packets that are handled by this rule</help>
</field>
<field>
<id>rule.interface</id>
<label>Interface</label>
<type>dropdown</type>
<help>Choose which interface this rule applies to. Hint: in most cases, you'll want to use WAN here</help>
</field>
<field>
<id>rule.type</id>
<label>Type</label>
<type>dropdown</type>
<help>
Select BINAT (default) or NAT here, when nets are equally sized binat is usually the best option.Using NAT we can also map unequal sized networks.
A BINAT rule specifies a bidirectional mapping between an external and internal network and can be used from both ends, nat only applies in one direction.
</help>
</field>
<field>
<id>rule.external</id>
<label>External network (Target)</label>
<type>text</type>
<help>Enter the external subnet's starting address for the 1:1 mapping or network. This is the address or network the traffic will translate to/from.</help>
</field>
<field>
<id>rule.source_not</id>
<label>Source / Invert</label>
<type>checkbox</type>
<advanced>true</advanced>
<help>Use this option to invert the sense of the match.</help>
</field>
<field>
<id>rule.source_net</id>
<label>Source / Internal</label>
<type>text</type>
<style>net_selector</style>
<help>Enter the internal subnet for the 1:1 mapping.</help>
</field>
<field>
<id>rule.destination_not</id>
<label>Destination / Invert</label>
<type>checkbox</type>
<advanced>true</advanced>
<help>Use this option to invert the sense of the match.</help>
</field>
<field>
<id>rule.destination_net</id>
<label>Destination</label>
<type>text</type>
<style>net_selector</style>
<help>The 1:1 mapping will only be used for connections to or from the specified destination. Hint: this is usually 'any'.</help>
</field>
<field>
<id>rule.categories</id>
<label>Categories</label>
<type>select_multiple</type>
<style>tokenize</style>
<help>For grouping purposes you may select multiple groups here to organize items.</help>
</field>
<field>
<id>rule.description</id>
<label>Description</label>
<type>text</type>
</field>
</form>

View File

@ -238,18 +238,6 @@
<pattern>api/firewall/category/*</pattern>
</patterns>
</page-firewall-categories>
<page-firewall-nat-1-1>
<name>Firewall: NAT: 1:1</name>
<patterns>
<pattern>firewall_nat_1to1.php*</pattern>
</patterns>
</page-firewall-nat-1-1>
<page-firewall-nat-1-1-edit>
<name>Firewall: NAT: 1:1: Edit</name>
<patterns>
<pattern>firewall_nat_1to1_edit.php*</pattern>
</patterns>
</page-firewall-nat-1-1-edit>
<page-firewall-nat-outbound>
<name>Firewall: NAT: Outbound</name>
<patterns>

View File

@ -133,9 +133,6 @@
<PortForward order="100" VisibleName="Port Forward" url="/firewall_nat.php">
<Edit url="/firewall_nat_edit.php*" visibility="hidden"/>
</PortForward>
<OneToOne order="200" VisibleName="One-to-One" url="/firewall_nat_1to1.php">
<Edit url="/firewall_nat_1to1_edit.php*" visibility="hidden"/>
</OneToOne>
<Outbound order="300" VisibleName="Outbound" url="/firewall_nat_out.php">
<Edit url="/firewall_nat_out_edit.php*" visibility="hidden"/>
</Outbound>

View File

@ -20,4 +20,11 @@
<pattern>api/firewall/npt/*</pattern>
</patterns>
</page-firewall-nat-npt>
<page-firewall-nat-1-1-edit>
<name>Firewall: NAT: 1:1</name>
<patterns>
<pattern>ui/firewall/one_to_one/*</pattern>
<pattern>api/firewall/one_to_one/*</pattern>
</patterns>
</page-firewall-nat-1-1-edit>
</acl>

View File

@ -134,6 +134,44 @@ class Filter extends BaseModel
}
}
}
/* 1 to 1 mappings */
foreach ($this->onetoone->rule->iterateItems() as $rule) {
if ($validateFullModel || $rule->isFieldChanged()) {
$ipprotos = [];
$subnets = [];
foreach (['source_net', 'destination_net', 'external'] as $fieldname) {
$subnets[$fieldname] = null;
if (Util::isSubnet($rule->$fieldname) || Util::isIpAddress($rule->$fieldname)) {
$ipprotos[$fieldname] = strpos($rule->$fieldname, ':') === false ? "inet" : "inet6";
$subnet_default = $ipprotos[$fieldname] == 'inet' ? '32' : '128';
$subnets[$fieldname] = explode('/', $rule->$fieldname . '/'.$subnet_default)[1];
}
}
if (count(array_unique(array_values($ipprotos))) > 1) {
foreach (array_keys($ipprotos) as $fieldname) {
$messages->appendMessage(new Message(
gettext("IP protocol families should match."),
$rule->$fieldname->__reference
));
}
}
if ($rule->type == 'binat' && !empty((string)$rule->enabled)) {
/* binat rules are more strict, when not enabled, we may skip the validations to ease migration */
if (empty($ipprotos['source_net'])) {
$messages->appendMessage(new Message(
gettext("For BINAT rules only addresses or subnets are allowed."),
$rule->source_net->__reference
));
} elseif ($subnets['external'] != $subnets['source_net']) {
$messages->appendMessage(new Message(
gettext("External subnet should match internal subnet."),
$rule->external->__reference
));
}
}
}
}
return $messages;
}

View File

@ -1,6 +1,6 @@
<model>
<mount>//OPNsense/Firewall/Filter</mount>
<version>1.0.3</version>
<version>1.0.4</version>
<migration_prefix>MFP</migration_prefix>
<description>OPNsense firewall filter rules</description>
<items>
@ -245,5 +245,75 @@
<description type="DescriptionField"/>
</rule>
</npt>
<onetoone>
<rule type=".\SourceNatRuleField">
<enabled type="BooleanField">
<Default>1</Default>
<Required>Y</Required>
</enabled>
<log type="BooleanField">
<Default>0</Default>
<Required>Y</Required>
</log>
<sequence type="IntegerField">
<MinimumValue>1</MinimumValue>
<MaximumValue>99999</MaximumValue>
<ValidationMessage>provide a valid sequence for sorting</ValidationMessage>
<Required>Y</Required>
<Default>1</Default>
</sequence>
<interface type="InterfaceField">
<Required>Y</Required>
<Default>wan</Default>
<AllowDynamic>Y</AllowDynamic>
</interface>
<type type="OptionField">
<Default>binat</Default>
<Required>Y</Required>
<OptionValues>
<binat>BINAT</binat>
<nat>NAT</nat>
</OptionValues>
</type>
<source_net type="NetworkAliasField">
<Required>Y</Required>
</source_net>
<source_not type="BooleanField">
<Default>0</Default>
<Required>Y</Required>
</source_not>
<destination_net type="NetworkAliasField">
<Required>Y</Required>
<Default>any</Default>
</destination_net>
<destination_not type="BooleanField">
<Default>0</Default>
<Required>Y</Required>
</destination_not>
<external type="NetworkField">
<Required>Y</Required>
<WildcardEnabled>N</WildcardEnabled>
</external>
<natreflection type="OptionField">
<OptionValues>
<default value=''>default</default>
<enable>Enable</enable>
<disable>Disable</disable>
</OptionValues>
</natreflection>
<categories type="ModelRelationField">
<Model>
<rulesets>
<source>OPNsense.Firewall.Category</source>
<items>categories.category</items>
<display>name</display>
</rulesets>
</Model>
<Multiple>Y</Multiple>
<ValidationMessage>Related category not found.</ValidationMessage>
</categories>
<description type="DescriptionField"/>
</rule>
</onetoone>
</items>
</model>

View File

@ -9,6 +9,9 @@
</SourceNat>
</Automation>
<NAT>
<OneToOne order="200" VisibleName="One-to-One" url="/ui/firewall/one_to_one/">
<FilterRef url="/ui/firewall/one_to_one#*" visibility="hidden"/>
</OneToOne>
<NPTv6 order="400" url="/ui/firewall/npt/">
<FilterRef url="/ui/firewall/npt#*" visibility="hidden"/>
</NPTv6>

View File

@ -0,0 +1,107 @@
<?php
/**
* Copyright (C) 2024 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\Firewall\Migrations;
use OPNsense\Core\Config;
use OPNsense\Base\BaseModelMigration;
use OPNsense\Firewall\Filter;
use OPNsense\Firewall\Category;
use OPNsense\Firewall\Util;
// <!-- external, category, descr, interface, type, src, dst, natreflection, disabled -->
class MFP1_0_4 extends BaseModelMigration
{
public function run($model)
{
if ($model instanceof Filter) {
$sequence = 1;
$catmdl = new Category();
foreach ((Config::getInstance()->object())->nat->children() as $child) {
if ($child->getName() == 'onetoone') {
$addr = [] ;
foreach (['destination', 'source'] as $fieldname) {
if (!empty(((string)$child->$fieldname->any))) {
$addr[$fieldname] = 'any';
} elseif (Util::isSubnet((string)$child->$fieldname->address)) {
$addr[$fieldname] = (string)$child->$fieldname->address;
} elseif (Util::isIpAddress((string)$child->$fieldname->address)) {
$subn = strpos($child->$fieldname->address, ':') === false ? '32' : '128';
$addr[$fieldname] = (string)$child->$fieldname->address . '/' . $subn;
} elseif (!empty((string)$child->$fieldname->address)) {
$addr[$fieldname] = (string)$child->$fieldname->address;
} elseif (!empty((string)$child->$fieldname->network)) {
$addr[$fieldname] = (string)$child->$fieldname->network;
} else {
$addr[$fieldname] = null;
}
}
if (!empty($addr['source']) && !empty($addr['destination']) && !empty((string)$child->external)) {
$node = $model->onetoone->rule->Add();
$node->enabled = empty((string)$child->disabled) ? "1" : "0";
$node->log = empty((string)$child->log) ? "0" : "1";
$node->sequence = (string)($sequence++);
if (!empty((string)$child->category)) {
$cats = [];
foreach (explode(',', (string)$child->category) as $cat) {
$tmp = $catmdl->getByName($cat);
if ($tmp != null) {
$cats[] = $tmp->getAttributes()['uuid'];
}
}
$node->categories = implode(",", $cats);
}
$node->interface = (string)$child->interface;
$node->type = !empty((string)$child->type) ? (string)$child->type : 'binat';
$node->external = (string)$child->external;
$node->source_net = $addr['source'];
$node->destination_net = $addr['source'];
$node->source_not = !empty((string)$child->source->not) ? '1' : '0';
$node->destination_not = !empty((string)$child->destination->not) ? '1' : '0';
$node->description = (string)$child->descr;
}
}
}
}
}
public function post($model)
{
if ($model instanceof Filter) {
$cfgObj = Config::getInstance()->object();
if (isset($cfgObj->nat->onetoone)) {
unset($cfgObj->nat->onetoone);
}
}
}
}

View File

@ -1,410 +0,0 @@
<?php
/*
* Copyright (C) 2014 Deciso B.V.
* Copyright (C) 2003-2004 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("filter.inc");
$a_1to1 = &config_read_array('nat', 'onetoone');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$pconfig = $_POST;
if (isset($pconfig['id']) && isset($a_1to1[$pconfig['id']])) {
// id found and valid
$id = $pconfig['id'];
}
if (isset($pconfig['apply'])) {
filter_configure();
$savemsg = get_std_save_message();
clear_subsystem_dirty('natconf');
clear_subsystem_dirty('filter');
} elseif (isset($pconfig['action']) && $pconfig['action'] == 'del' && isset($id)) {
// delete single entry
unset($a_1to1[$id]);
write_config();
mark_subsystem_dirty('natconf');
header(url_safe('Location: /firewall_nat_1to1.php'));
exit;
} elseif (isset($pconfig['action']) && $pconfig['action'] == 'del_x' && isset($pconfig['rule']) && count($pconfig['rule']) > 0) {
// delete selected
foreach ($pconfig['rule'] as $rulei) {
unset($a_1to1[$rulei]);
}
write_config();
mark_subsystem_dirty('natconf');
header(url_safe('Location: /firewall_nat_1to1.php'));
exit;
} elseif (isset($pconfig['action']) && in_array($pconfig['action'], array('toggle_enable', 'toggle_disable')) && isset($pconfig['rule']) && count($pconfig['rule']) > 0) {
foreach ($pconfig['rule'] as $rulei) {
$a_1to1[$rulei]['disabled'] = $pconfig['action'] == 'toggle_disable';
}
write_config();
mark_subsystem_dirty('natconf');
header(url_safe('Location: /firewall_nat_1to1.php'));
exit;
} elseif (isset($pconfig['action']) && $pconfig['action'] == 'move') {
// move selected
if (isset($pconfig['rule']) && count($pconfig['rule']) > 0) {
// if rule not set/found, move to end
if (!isset($id)) {
$id = count($a_1to1);
}
$a_1to1 = legacy_move_config_list_items($a_1to1, $id, $pconfig['rule']);
write_config();
mark_subsystem_dirty('natconf');
header(url_safe('Location: /firewall_nat_1to1.php'));
exit;
}
} elseif (isset($pconfig['action']) && $pconfig['action'] == 'toggle' && isset($id)) {
// toggle item
if(isset($a_1to1[$id]['disabled'])) {
unset($a_1to1[$id]['disabled']);
} else {
$a_1to1[$id]['disabled'] = true;
}
write_config('Toggled NAT 1:1 rule');
mark_subsystem_dirty('natconf');
header(url_safe('Location: /firewall_nat_1to1.php'));
exit;
}
}
legacy_html_escape_form_data($a_1to1);
include("head.inc");
?>
<body>
<script>
$( document ).ready(function() {
// link delete buttons
$(".act_delete").click(function(event){
event.preventDefault();
var id = $(this).attr("id").split('_').pop(-1);
if (id != 'x') {
// delete single
BootstrapDialog.show({
type:BootstrapDialog.TYPE_DANGER,
title: "<?= gettext("1:1");?>",
message: "<?=gettext("Do you really want to delete this rule?");?>",
buttons: [{
label: "<?= gettext("No");?>",
action: function(dialogRef) {
dialogRef.close();
}}, {
label: "<?= gettext("Yes");?>",
action: function(dialogRef) {
$("#id").val(id);
$("#action").val("del");
$("#iform").submit()
}
}]
});
} else {
// delete selected
BootstrapDialog.show({
type:BootstrapDialog.TYPE_DANGER,
title: "<?= gettext("1:1");?>",
message: "<?=gettext("Do you really want to delete the selected rules?");?>",
buttons: [{
label: "<?= gettext("No");?>",
action: function(dialogRef) {
dialogRef.close();
}}, {
label: "<?= gettext("Yes");?>",
action: function(dialogRef) {
$("#id").val("");
$("#action").val("del_x");
$("#iform").submit()
}
}]
});
}
});
// enable/disable selected
$(".act_toggle_enable").click(function(event){
event.preventDefault();
BootstrapDialog.show({
type:BootstrapDialog.TYPE_DANGER,
title: "<?= gettext("Rules");?>",
message: "<?=gettext("Enable selected rules?");?>",
buttons: [{
label: "<?= gettext("No");?>",
action: function(dialogRef) {
dialogRef.close();
}}, {
label: "<?= gettext("Yes");?>",
action: function(dialogRef) {
$("#id").val("");
$("#action").val("toggle_enable");
$("#iform").submit()
}
}]
});
});
$(".act_toggle_disable").click(function(event){
event.preventDefault();
BootstrapDialog.show({
type:BootstrapDialog.TYPE_DANGER,
title: "<?= gettext("Rules");?>",
message: "<?=gettext("Disable selected rules?");?>",
buttons: [{
label: "<?= gettext("No");?>",
action: function(dialogRef) {
dialogRef.close();
}}, {
label: "<?= gettext("Yes");?>",
action: function(dialogRef) {
$("#id").val("");
$("#action").val("toggle_disable");
$("#iform").submit()
}
}]
});
});
// link move buttons
$(".act_move").click(function(event){
event.preventDefault();
var id = $(this).attr("id").split('_').pop(-1);
$("#id").val(id);
$("#action").val("move");
$("#iform").submit();
});
// link toggle buttons
$(".act_toggle").click(function(event){
event.preventDefault();
var id = $(this).attr("id").split('_').pop(-1);
$("#id").val(id);
$("#action").val("toggle");
$("#iform").submit();
});
// select All
$("#selectAll").click(function(){
$(".rule_select:not(:disabled)").prop("checked", $(this).prop("checked"));
});
// watch scroll position and set to last known on page load
watchScrollPosition();
// move category block
$("#category_block").detach().appendTo($(".page-content-head > .container-fluid > .list-inline"));
$("#category_block").addClass("pull-right");
// our usual zebra striping doesn't respect hidden rows, hook repaint on .opnsense-rules change() and fire initially
$(".opnsense-rules > tbody > tr").each(function(){
// save zebra color
let tr_color = $(this).children(0).css("background-color");
if (tr_color != 'transparent' && !tr_color.includes('(0, 0, 0')) {
$("#fw_category").data('stripe_color', tr_color);
}
});
$(".opnsense-rules").removeClass("table-striped");
$(".opnsense-rules").change(function(){
$(".opnsense-rules > tbody > tr:visible").each(function (index) {
$(this).css("background-color", "inherit");
if ( index % 2 == 0) {
$(this).css("background-color", $("#fw_category").data('stripe_color'));
}
});
});
// hook category functionality
hook_firewall_categories();
});
</script>
<?php include("fbegin.inc"); ?>
<div class="hidden">
<div id="category_block" style="z-index:-100;">
<select class="selectpicker hidden-xs hidden-sm hidden-md" data-live-search="true" data-size="5" multiple title="<?=gettext("Select category");?>" id="fw_category">
</select>
</div>
</div>
<section class="page-content-main">
<div class="container-fluid">
<div class="row">
<?php
print_service_banner('firewall');
if (isset($savemsg))
print_info_box($savemsg);
if (is_subsystem_dirty('natconf'))
print_info_box_apply(gettext("The NAT configuration has been changed.") .
"<br />" .
gettext("You must apply the changes in order for them to take effect."));
?>
<section class="col-xs-12">
<div class="content-box">
<form method="post" name="iform" id="iform">
<input type="hidden" id="id" name="id" value="" />
<input type="hidden" id="action" name="action" value="" />
<table class="table table-striped table-condensed opnsense-rules">
<thead>
<tr>
<th><input type="checkbox" id="selectAll"></th>
<th>&nbsp;</th>
<th><?=gettext("Interface"); ?></th>
<th><?=gettext("External IP"); ?></th>
<th><?=gettext("Internal IP"); ?></th>
<th><?=gettext("Destination IP"); ?></th>
<th><?=gettext("Description"); ?></th>
<th class="text-nowrap">
<a href="firewall_nat_1to1_edit.php" class="btn btn-primary btn-xs" data-toggle="tooltip" title="<?= html_safe(gettext('Add')) ?>">
<i class="fa fa-plus fa-fw"></i>
</a>
<?php if (count($a_1to1)): ?>
<button id="move_<?= count($a_1to1) ?>" name="move_<?= count($a_1to1) ?>_x" data-toggle="tooltip" title="<?= html_safe(gettext('Move selected rules to end')) ?>" class="act_move btn btn-default btn-xs">
<i class="fa fa-arrow-left fa-fw"></i>
</button>
<button id="del_x" title="<?= html_safe(gettext('Delete selected')) ?>" data-toggle="tooltip" class="act_delete btn btn-default btn-xs">
<i class="fa fa-trash fa-fw"></i>
</button>
<button title="<?= html_safe(gettext('Enable selected')) ?>" data-toggle="tooltip" class="act_toggle_enable btn btn-default btn-xs">
<i class="fa fa-check-square-o fa-fw"></i>
</button>
<button title="<?= html_safe(gettext('Disable selected')) ?>" data-toggle="tooltip" class="act_toggle_disable btn btn-default btn-xs">
<i class="fa fa-square-o fa-fw"></i>
</button>
</th>
<?php endif ?>
</tr>
</thead>
<tbody>
<?php
$i = 0;
foreach ($a_1to1 as $natent):
?>
<tr class="rule <?=isset($natent['disabled'])?"text-muted":"";?>" data-category="<?=!empty($natent['category']) ? $natent['category'] : "";?>" ondblclick="document.location='firewall_nat_1to1_edit.php?id=<?=$i;?>';">
<td>
<input class="rule_select" type="checkbox" name="rule[]" value="<?=$i;?>" />
</td>
<td>
<a href="#" type="submit" id="toggle_<?=$i;?>" data-toggle="tooltip" title="<?=(!isset($natent['disabled'])) ? gettext("Disable") : gettext("Enable");?>" class="act_toggle">
<?php if(isset($natent['disabled'])):?>
<span class="fa fa-play text-muted"></span>
<?php else:?>
<span class="fa fa-play text-success"></span>
<?php endif; ?>
</a>
</td>
<td>
<?=htmlspecialchars(convert_friendly_interface_to_friendly_descr(isset($natent['interface']) ? $natent['interface'] : "wan"));?>
</td>
<td>
<?=isset($natent['external']) ? $natent['external'] : "";?><?=isset($natent['source']) && strpos($natent['external'], '/') === false ? strstr(pprint_address($natent['source']), '/') : "";?>
<?php if (isset($natent['external']['address']) && is_alias($natent['external']['address'])): ?>
&nbsp;<a href="/ui/firewall/alias/index/<?=htmlspecialchars($natent['external']['address']);?>"><i class="fa fa-list"></i> </a>
<?php endif; ?>
</td>
<td>
<?php if (isset($natent['source']['address']) && is_alias($natent['source']['address'])): ?>
<span title="<?=htmlspecialchars(get_alias_description($natent['source']['address']));?>" data-toggle="tooltip" data-html="true">
<?=htmlspecialchars(pprint_address($natent['source']));?>&nbsp;
</span>
<a href="/ui/firewall/alias/index/<?=htmlspecialchars($natent['source']['address']);?>"
title="<?=gettext("edit alias");?>" data-toggle="tooltip">
<i class="fa fa-list"></i>
</a>
<?php else: ?>
<?=htmlspecialchars(pprint_address($natent['source']));?>
<?php endif; ?>
</td>
<td>
<?php if (isset($natent['destination']['address']) && is_alias($natent['destination']['address'])): ?>
<span title="<?=htmlspecialchars(get_alias_description($natent['destination']['address']));?>" data-toggle="tooltip" data-html="true">
<?=htmlspecialchars(pprint_address($natent['destination']));?>&nbsp;
</span>
<a href="/ui/firewall/alias/index/<?=htmlspecialchars($natent['destination']['address']);?>"
title="<?=gettext("edit alias");?>" data-toggle="tooltip">
<i class="fa fa-list"></i>
</a>
<?php else: ?>
<?=htmlspecialchars(pprint_address($natent['destination']));?>
<?php endif; ?>
</td>
<td class="rule-description">
<?=$natent['descr'];?> &nbsp;
</td>
<td>
<a type="submit" id="move_<?=$i;?>" name="move_<?=$i;?>_x" data-toggle="tooltip" title="<?= html_safe(gettext("Move selected rules before this rule")) ?>" class="act_move btn btn-default btn-xs">
<span class="fa fa-arrow-left fa-fw"></span>
</a>
<a href="firewall_nat_1to1_edit.php?id=<?=$i;?>" class="btn btn-default btn-xs" data-toggle="tooltip" title="<?= html_safe(gettext('Edit')) ?>">
<span class="fa fa-pencil fa-fw"></span>
</a>
<a id="del_<?=$i;?>" title="<?= html_safe(gettext('Delete')) ?>" data-toggle="tooltip" class="act_delete btn btn-default btn-xs">
<span class="fa fa-trash fa-fw"></span>
</a>
<a href="firewall_nat_1to1_edit.php?dup=<?=$i;?>" data-toggle="tooltip" title="<?= html_safe(gettext('Clone')) ?>" class="btn btn-default btn-xs">
<span class="fa fa-clone fa-fw"></span>
</a>
</td>
</tr>
<?php
$i++;
endforeach;
?>
</tbody>
<tfoot>
<tr>
<td style="width:16px"><span class="fa fa-play text-success"></span></td>
<td colspan="8"><?=gettext("Enabled rule"); ?></td>
</tr>
<tr>
<td><span class="fa fa-play text-muted"></span></td>
<td colspan="8"><?=gettext("Disabled rule"); ?></td>
</tr>
<tr>
<td><a><i class="fa fa-list"></i></a></td>
<td colspan="8"><?=gettext("Alias (click to view/edit)");?></td>
</tr>
<tr>
<td colspan="9">
<?=gettext("If you add a 1:1 NAT entry for any of the interface IPs on this system, " .
"it will make this system inaccessible on that IP address. i.e. if " .
"you use your WAN IP address, any services on this system (IPsec, OpenVPN server, etc.) " .
"using the WAN IP address will no longer function."); ?>
</td>
</tr>
</tfoot>
</table>
</form>
</div>
</section>
</div>
</div>
</section>
<?php include("foot.inc"); ?>

View File

@ -1,475 +0,0 @@
<?php
/*
* Copyright (C) 2014 Deciso B.V.
* Copyright (C) 2003-2004 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");
$a_1to1 = &config_read_array('nat', 'onetoone');
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// input record id, if valid
if (isset($_GET['dup']) && isset($a_1to1[$_GET['dup']])) {
$configId = $_GET['dup'];
} elseif (isset($_GET['id']) && isset($a_1to1[$_GET['id']])) {
$id = $_GET['id'];
$configId = $id;
}
$pconfig = array();
// set defaults
$pconfig['interface'] = "wan";
$pconfig['src'] = 'lan';
$pconfig['dst'] = 'any';
$pconfig['type'] = 'binat';
if (isset($configId)) {
// copy settings from config
foreach (array('disabled','interface','external','descr','natreflection', 'type', 'category') as $fieldname) {
if (isset($a_1to1[$configId][$fieldname])) {
$pconfig[$fieldname] = $a_1to1[$configId][$fieldname];
} else {
$pconfig[$fieldname] = null;
}
}
// read settings with some kind of logic
address_to_pconfig(
$a_1to1[$configId]['source'], $pconfig['src'],
$pconfig['srcmask'], $pconfig['srcnot'],
$pconfig['__unused__'],$pconfig['__unused__']
);
address_to_pconfig(
$a_1to1[$configId]['destination'], $pconfig['dst'],
$pconfig['dstmask'], $pconfig['dstnot'],
$pconfig['__unused__'],$pconfig['__unused__']
);
} else {
// init form data on new
foreach (array('disabled','interface','external','descr','natreflection'
,'src','srcmask','srcnot','dst','dstmask','dstnot'
) as $fieldname) {
if (!isset($pconfig[$fieldname])) {
$pconfig[$fieldname] = null;
}
}
}
$pconfig['category'] = !empty($pconfig['category']) ? explode(",", $pconfig['category']) : [];
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input_errors = array();
$pconfig = $_POST;
// input record id, if valid
if (isset($_POST['id']) && isset($a_1to1[$_POST['id']])) {
$id = $_POST['id'];
}
// trim input
foreach (array('external','src','dst') as $fieldname) {
if (isset($pconfig[$fieldname])) {
$pconfig[$fieldname] = trim($pconfig[$fieldname]);
}
}
/* input validation */
$reqdfields = explode(" ", "interface external src dst");
$reqdfieldsn = array(gettext("Interface"), gettext("External subnet"), gettext("Source address"), gettext("Destination address"));
do_input_validation($pconfig, $reqdfields, $reqdfieldsn, $input_errors);
/* For external, user can enter only ip's */
$tmpext = explode('/', $pconfig['external']);
if (!empty($pconfig['external'])) {
if ($pconfig['type'] == 'binat' && (!is_ipaddr($tmpext[0]) || (count($tmpext) != 1 && $pconfig['srcmask'] != $tmpext[1]))) {
$input_errors[] = gettext("A valid external subnet must be specified.");
} elseif ($pconfig['type'] == 'nat' && !is_subnet($pconfig['external'])) {
$input_errors[] = gettext("A valid external subnet must be specified.");
}
}
/* For src, user can enter only ip's or networks */
if ($pconfig['type'] == 'binat' && !is_subnet($pconfig['src']) && !is_ipaddr($pconfig['src'])) {
$input_errors[] = sprintf(gettext("%s is not a valid source IP address."), $pconfig['src']);
} elseif (!is_specialnet($pconfig['src']) && !is_ipaddroralias($pconfig['src'])) {
$input_errors[] = sprintf(gettext("%s is not a valid source IP address or alias."), $pconfig['src']);
}
if (!empty($pconfig['srcmask']) && !is_numericint($pconfig['srcmask'])) {
$input_errors[] = gettext("A valid source bit count must be specified.");
}
/* For dst, user can enter ip's, networks or aliases */
if (!is_specialnet($pconfig['dst']) && !is_ipaddroralias($pconfig['dst'])) {
$input_errors[] = sprintf(gettext("%s is not a valid destination IP address or alias."), $pconfig['dst']);
}
if (!empty($pconfig['dstmask']) && !is_numericint($pconfig['dstmask'])) {
$input_errors[] = gettext("A valid destination bit count must be specified.");
}
if (count($input_errors) == 0) {
$natent = array();
// 1-on-1 copy
$natent['external'] = $pconfig['external'];
$natent['category'] = !empty($pconfig['category']) ? implode(",", $pconfig['category']) : null;
$natent['descr'] = $pconfig['descr'];
$natent['interface'] = $pconfig['interface'];
$natent['type'] = $pconfig['type'];
// copy form data with some kind of logic in it
$natent['disabled'] = isset($_POST['disabled']) ? true:false;
pconfig_to_address($natent['source'], $pconfig['src'],
$pconfig['srcmask'], !empty($pconfig['srcnot']));
pconfig_to_address($natent['destination'], $pconfig['dst'],
$pconfig['dstmask'], !empty($pconfig['dstnot']));
if (isset($pconfig['natreflection'] ) && ($pconfig['natreflection'] == "enable" || $pconfig['natreflection'] == "disable")) {
$natent['natreflection'] = $pconfig['natreflection'];
}
// save data
if (isset($id)) {
$a_1to1[$id] = $natent;
} else {
$a_1to1[] = $natent;
}
OPNsense\Core\Config::getInstance()->fromArray($config);
$catmdl = new OPNsense\Firewall\Category();
if ($catmdl->sync()) {
$catmdl->serializeToConfig();
$config = OPNsense\Core\Config::getInstance()->toArray(listtags());
}
write_config();
mark_subsystem_dirty('natconf');
header(url_safe('Location: /firewall_nat_1to1.php'));
exit;
}
}
legacy_html_escape_form_data($pconfig);
include("head.inc");
?>
<body>
<script src="<?= cache_safe('/ui/js/tokenize2.js') ?>"></script>
<link rel="stylesheet" type="text/css" href="<?= cache_safe(get_themed_filename('/css/tokenize2.css')) ?>">
<script src="<?= cache_safe('/ui/js/opnsense_ui.js') ?>"></script>
<script>
$( document ).ready(function() {
// select / input combination, link behaviour
// when the data attribute "data-other" is selected, display related input item(s)
// push changes from input back to selected option value
$('[for!=""][for]').each(function(){
var refObj = $("#"+$(this).attr("for"));
if (refObj.is("select")) {
// connect on change event to select box (show/hide)
refObj.change(function(){
if ($(this).find(":selected").attr("data-other") == "true") {
// show related controls
$('*[for="'+$(this).attr("id")+'"]').each(function(){
if ($(this).hasClass("selectpicker")) {
$(this).selectpicker('show');
} else {
$(this).removeClass("hidden");
}
$(this).prop("disabled", false);
});
} else {
// hide related controls
$('*[for="'+$(this).attr("id")+'"]').each(function(){
if ($(this).hasClass("selectpicker")) {
$(this).selectpicker('hide');
} else {
$(this).addClass("hidden");
}
$(this).prop("disabled", true);
});
}
});
// update initial
refObj.change();
// connect on change to input to save data to selector
if ($(this).attr("name") == undefined) {
$(this).change(function(){
var otherOpt = $('#'+$(this).attr('for')+' > option[data-other="true"]') ;
otherOpt.attr("value",$(this).val());
});
}
}
});
// aliases and "special nets" are only allowed for nat type entries
$("#nattype").change(function(){
if ($(this).val() == 'binat') {
$("#src optgroup[data-type='nat']").children().prop('disabled', true);
} else {
$("#src optgroup[data-type='nat']").children().prop('disabled', false);
}
$("#src").selectpicker('refresh');
});
$("#nattype").change();
formatTokenizersUI();
});
</script>
<?php include("fbegin.inc"); ?>
<section class="page-content-main">
<div class="container-fluid">
<div class="row">
<?php
if (isset($input_errors) && count($input_errors) > 0)
print_input_errors($input_errors);
?>
<section class="col-xs-12">
<div class="content-box">
<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%"><strong><?= gettext('Edit NAT 1:1 entry') ?></strong></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><a id="help_for_disabled" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Disabled"); ?></td>
<td>
<input name="disabled" type="checkbox" id="disabled" value="yes" <?= !empty($pconfig['disabled']) ? "checked=\"checked\"" : ""; ?> />
<div class="hidden" data-for="help_for_disabled">
<strong><?=gettext("Disable this rule"); ?></strong><br />
<?=gettext("Set this option to disable this rule without removing it from the list."); ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_interface" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Interface"); ?></td>
<td>
<select name="interface" class="selectpicker" data-width="348px" data-live-search="true">
<?php
foreach (legacy_config_get_interfaces(array("enable" => true)) as $iface => $ifdetail): ?>
<option value="<?=$iface;?>" <?= $iface == $pconfig['interface'] ? "selected=\"selected\"" : ""; ?>>
<?=htmlspecialchars($ifdetail['descr']);?>
</option>
<?php endforeach; ?>
</select>
<div class="hidden" data-for="help_for_interface">
<?=gettext("Choose which interface this rule applies to"); ?>.<br />
<?=gettext("Hint: in most cases, you'll want to use WAN here"); ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_type" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Type"); ?></td>
<td>
<select name="type" class="selectpicker" data-width="348px" id="nattype">
<option value="binat" <?=$pconfig['type'] == 'binat' || empty($pconfig['type']) ? "selected=\"selected\"" : ""; ?>>
<?=gettext("BINAT");?>
</option>
<option value="nat" <?=$pconfig['type'] == 'nat' ? "selected=\"selected\"" : ""; ?>>
<?=gettext("NAT");?>
</option>
</select>
<div class="hidden" data-for="help_for_type">
<?=gettext("Select BINAT (default) or NAT here, when nets are equally sized binat is usually the best option.".
"Using NAT we can also map unequal sized networks.");?><br />
<?=gettext("A BINAT rule specifies a bidirectional mapping between an external and internal network and can be used from both ends, nat only applies in one direction.");?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_external" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("External network"); ?></td>
<td>
<input name="external" type="text" value="<?=$pconfig['external'];?>" />
<div class="hidden" data-for="help_for_external">
<?=gettext("Enter the external subnet's starting address for the 1:1 mapping or network.");?><br />
<?=gettext("The subnet mask from the internal address below will be applied to this IP address, when none is provided."); ?><br />
<?=gettext("This is the address or network the traffic will translate to/from.");?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_src_invert" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Source") . " / ".gettext("Invert");?> </td>
<td>
<input name="srcnot" type="checkbox" id="srcnot" value="yes" <?= !empty($pconfig['srcnot']) ? "checked=\"checked\"" : "";?> />
<div class="hidden" data-for="help_for_src_invert">
<?=gettext("Use this option to invert the sense of the match."); ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_src" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Source"); ?></td>
<td>
<table style="max-width: 348px">
<tr>
<td>
<select name="src" id="src" class="selectpicker" data-live-search="true" data-size="5" data-width="348px" data-hide-disabled="true">
<option data-other=true value="<?=$pconfig['src'];?>" <?=!is_specialnet($pconfig['src']) ? "selected=\"selected\"" : "";?>><?=gettext("Single host or Network"); ?></option>
<optgroup label="<?=gettext("Aliases");?>" data-type="nat">
<?php foreach (legacy_list_aliases("network") as $alias):
?>
<option value="<?=$alias['name'];?>" <?=$alias['name'] == $pconfig['src'] ? "selected=\"selected\"" : "";?>><?=htmlspecialchars($alias['name']);?></option>
<?php endforeach; ?>
</optgroup>
<optgroup label="<?=gettext("Networks");?>" data-type="nat">
<?php foreach (get_specialnets(true) as $ifent => $ifdesc):
?>
<option value="<?=$ifent;?>" <?= $pconfig['src'] == $ifent ? "selected=\"selected\"" : ""; ?>><?=$ifdesc;?></option>
<?php endforeach; ?>
</optgroup>
</select>
</td>
</tr>
</table>
<!-- updates to "other" option in src -->
<table style="max-width: 348px">
<tr>
<td style="width:285px">
<input type="text" for="src" value="<?=$pconfig['src'];?>" aria-label="<?=gettext("Source address");?>"/>
</td>
<td>
<select name="srcmask" class="selectpicker" data-size="5" id="srcmask" data-width="70px" for="src" >
<?php for ($i = 32; $i > 0; $i--): ?>
<option value="<?=$i;?>" <?= $i == $pconfig['srcmask'] ? "selected=\"selected\"" : ""; ?>><?=$i;?></option>
<?php endfor; ?>
</select>
</td>
</tr>
</table>
<div class="hidden" data-for="help_for_src">
<?=gettext("Enter the internal subnet for the 1:1 mapping. The subnet size specified for the source will be applied to the external subnet, when none is provided."); ?>
</div>
</td>
</tr>
<tr>
<td> <a id="help_for_dst_invert" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Destination") . " / ".gettext("Invert");?> </td>
<td>
<input name="dstnot" type="checkbox" id="srcnot" value="yes" <?= !empty($pconfig['dstnot']) ? "checked=\"checked\"" : "";?> />
<div class="hidden" data-for="help_for_dst_invert">
<?=gettext("Use this option to invert the sense of the match."); ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_dst" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Destination"); ?></td>
<td>
<table style="max-width:348px">
<tr>
<td>
<select name="dst" id="dst" class="selectpicker" data-live-search="true" data-size="5" data-width="348px">
<option data-other=true value="<?=$pconfig['dst'];?>" <?=!is_specialnet($pconfig['dst']) ? "selected=\"selected\"" : "";?>><?=gettext("Single host or Network"); ?></option>
<optgroup label="<?=gettext("Aliases");?>">
<?php foreach (legacy_list_aliases("network") as $alias):
?>
<option value="<?=$alias['name'];?>" <?=$alias['name'] == $pconfig['dst'] ? "selected=\"selected\"" : "";?>><?=htmlspecialchars($alias['name']);?></option>
<?php endforeach; ?>
</optgroup>
<optgroup label="<?=gettext("Networks");?>">
<?php foreach (get_specialnets(true) as $ifent => $ifdesc):
?>
<option value="<?=$ifent;?>" <?= $pconfig['dst'] == $ifent ? "selected=\"selected\"" : ""; ?>><?=$ifdesc;?></option>
<?php endforeach; ?>
</optgroup>
</select>
</td>
</tr>
</table>
<!-- updates to "other" option in dst -->
<table style="max-width:348px">
<tr>
<td style="width:285px">
<input type="text" for="dst" value="<?= !is_specialnet($pconfig['dst']) ? $pconfig['dst'] : "";?>" aria-label="<?=gettext("Destination address");?>"/>
</td>
<td>
<select name="dstmask" class="selectpicker" data-size="5" id="dstmask" data-width="70px" for="dst" >
<?php for ($i = 32; $i > 0; $i--): ?>
<option value="<?=$i;?>" <?= $i == $pconfig['dstmask'] ? "selected=\"selected\"" : ""; ?>><?=$i;?></option>
<?php endfor; ?>
</select>
</td>
</tr>
</table>
<div class="hidden" data-for="help_for_dst">
<?=gettext("The 1:1 mapping will only be used for connections to or from the specified destination."); ?><br />
<?=gettext("Hint: this is usually 'any'."); ?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_category" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Category"); ?></td>
<td>
<select name="category[]" id="category" multiple="multiple" class="tokenize" data-allownew="true" data-width="348px" data-live-search="true">
<?php
foreach ((new OPNsense\Firewall\Category())->iterateCategories() as $category):
$catname = htmlspecialchars($category['name'], ENT_QUOTES | ENT_HTML401);?>
<option value="<?=$catname;?>" <?=!empty($pconfig['category']) && in_array($catname, $pconfig['category']) ? 'selected="selected"' : '';?> ><?=$catname;?></option>
<?php
endforeach;?>
</select>
<div class="hidden" data-for="help_for_category">
<?=gettext("You may enter or select a category here to group firewall rules (not parsed)."); ?>
</div>
</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>
</tr>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("NAT reflection"); ?></td>
<td>
<select name="natreflection" class="selectpicker">
<option value="default" <?=$pconfig['natreflection'] != "enable" && $pconfig['natreflection'] != "disable" ? "selected=\"selected\"" : ""; ?>><?=gettext("Use system default"); ?></option>
<option value="enable" <?=$pconfig['natreflection'] == "enable" ? "selected=\"selected\"" : ""; ?>><?=gettext("Enable"); ?></option>
<option value="disable" <?=$pconfig['natreflection'] == "disable" ? "selected=\"selected\"" : ""; ?>><?=gettext("Disable"); ?></option>
</select>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<input name="Submit" type="submit" class="btn btn-primary" value="<?=html_safe(gettext('Save')); ?>" />
<input type="button" class="btn btn-default" value="<?=html_safe(gettext('Cancel'));?>" onclick="window.location.href='/firewall_nat_1to1.php'" />
<?php if (isset($id)): ?>
<input name="id" type="hidden" value="<?=$id;?>" />
<?php endif; ?>
</td>
</tr>
</table>
</div>
</form>
</div>
</section>
</div>
</div>
</section>
<?php include("foot.inc"); ?>