.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
##|+PRIV
##|*IDENT=page-firewall-nat-outbound-edit
##|*NAME=Firewall: NAT: Outbound: Edit
##|*DESCR=Allow access to the 'Firewall: NAT: Outbound: Edit' page.
##|*MATCH=firewall_nat_out_edit.php*
##|-PRIV
require_once("guiconfig.inc");
require_once("ipsec.inc");
require_once("filter.inc");
require_once("shaper.inc");
if (!is_array($config['nat']['outbound'])) {
$config['nat']['outbound'] = array();
}
if (!is_array($config['nat']['outbound']['rule'])) {
$config['nat']['outbound']['rule'] = array();
}
$a_out = &$config['nat']['outbound']['rule'];
if (!is_array($config['aliases']['alias'])) {
$config['aliases']['alias'] = array();
}
$a_aliases = &$config['aliases']['alias'];
if (isset($_REQUEST['id']) && is_numericint($_REQUEST['id'])) {
$id = $_REQUEST['id'];
}
if (isset($_REQUEST['after']) && (is_numericint($_REQUEST['after']) || $_REQUEST['after'] == "-1")) {
$after = $_REQUEST['after'];
}
if (isset($_REQUEST['dup']) && is_numericint($_REQUEST['dup'])) {
$id = $_REQUEST['dup'];
$after = $_REQUEST['dup'];
}
if (isset($id) && $a_out[$id]) {
if (isset($a_out[$id]['created']) && is_array($a_out[$id]['created'])) {
$pconfig['created'] = $a_out[$id]['created'];
}
if (isset($a_out[$id]['updated']) && is_array($a_out[$id]['updated'])) {
$pconfig['updated'] = $a_out[$id]['updated'];
}
$pconfig['protocol'] = $a_out[$id]['protocol'];
list($pconfig['source'], $pconfig['source_subnet']) = explode('/', $a_out[$id]['source']['network']);
if (!is_numeric($pconfig['source_subnet'])) {
$pconfig['source_subnet'] = 32;
}
$pconfig['sourceport'] = $a_out[$id]['sourceport'];
address_to_pconfig($a_out[$id]['destination'], $pconfig['destination'],
$pconfig['destination_subnet'], $pconfig['destination_not'],
$none, $none);
$pconfig['dstport'] = $a_out[$id]['dstport'];
$pconfig['natport'] = $a_out[$id]['natport'];
$pconfig['target'] = $a_out[$id]['target'];
if (strlen($pconfig['target']) > 0) {
// Deduce the target type and add to the front of the target string.
if (is_subnet($pconfig['target'])) {
$target_type = "S";
} elseif (is_ipaddr($pconfig['target'])) {
$target_type = "I";
} elseif (is_alias($pconfig['target'])) {
$target_type = "H";
} else {
$target_type = "O";
}
$pconfig['target'] = $target_type . $pconfig['target'];
}
$pconfig['targetip'] = $a_out[$id]['targetip'];
$pconfig['targetip_subnet'] = $a_out[$id]['targetip_subnet'];
$pconfig['poolopts'] = $a_out[$id]['poolopts'];
$pconfig['source_hash_key'] = $a_out[$id]['source_hash_key'];
$pconfig['interface'] = $a_out[$id]['interface'];
if (!$pconfig['interface']) {
$pconfig['interface'] = "wan";
}
$pconfig['descr'] = $a_out[$id]['descr'];
$pconfig['nonat'] = $a_out[$id]['nonat'];
$pconfig['disabled'] = isset($a_out[$id]['disabled']);
$pconfig['staticnatport'] = isset($a_out[$id]['staticnatport']);
$pconfig['nosync'] = isset($a_out[$id]['nosync']);
} else {
$pconfig['source_subnet'] = 24;
$pconfig['destination'] = "any";
$pconfig['destination_subnet'] = 24;
$pconfig['interface'] = "wan";
}
if (isset($_REQUEST['dup']) && is_numericint($_REQUEST['dup'])) {
unset($id);
}
if ($_POST['save']) {
if ($_POST['destination_type'] == "any") {
$_POST['destination'] = "any";
$_POST['destination_subnet'] = 24;
}
if ($_POST['source_type'] == "any") {
$_POST['source'] = "any";
$_POST['source_subnet'] = 24;
} elseif ($_POST['source_type'] == "(self)") {
$_POST['source'] = "(self)";
$_POST['source_subnet'] = 24;
}
unset($input_errors);
$pconfig = $_POST;
/* run through $_POST items encoding HTML entitles so that the user
* cannot think he is slick and perform a XSS attack on the unwilling
*/
foreach ($_POST as $key => $value) {
$temp = str_replace(">", "", $value);
$newpost = htmlentities($temp);
if ($newpost <> $temp) {
$input_errors[] = sprintf(gettext("Invalid characters detected (%s). Please remove invalid characters and save again."), $temp);
}
}
/* input validation */
$reqdfields = explode(" ", "interface protocol source source_subnet destination destination_subnet");
$reqdfieldsn = array(gettext("Interface"), gettext("Protocol"), gettext("Source"), gettext("Source bit count"), gettext("Destination"), gettext("Destination bit count"));
do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
$protocol_uses_ports = in_array($_POST['protocol'], explode(" ", "any tcp udp tcp/udp"));
if ($_POST['source']) {
$_POST['source'] = trim($_POST['source']);
}
if ($_POST['destination']) {
$_POST['destination'] = trim($_POST['destination']);
}
if ($_POST['targetip']) {
$_POST['targetip'] = trim($_POST['targetip']);
}
if ($_POST['sourceport']) {
$_POST['sourceport'] = trim($_POST['sourceport']);
}
if ($_POST['dstport']) {
$_POST['dstport'] = trim($_POST['dstport']);
}
if ($_POST['natport']) {
$_POST['natport'] = trim($_POST['natport']);
}
if (strlen($_POST['target']) > 0) {
// Strip the target code 1-char code from the front before validating and saving.
$_POST['target'] = substr($_POST['target'], 1);
}
if ($protocol_uses_ports && $_POST['sourceport'] <> "" && !(is_portoralias($_POST['sourceport']) || is_portrange($_POST['sourceport']))) {
$input_errors[] = gettext("A valid port or port alias must be supplied for the source port entry.");
}
if ($protocol_uses_ports && $_POST['dstport'] <> "" && !(is_portoralias($_POST['dstport']) || is_portrange($_POST['dstport']))) {
$input_errors[] = gettext("A valid port or port alias must be supplied for the destination port entry.");
}
if ($protocol_uses_ports && $_POST['natport'] <> "" && !(is_portoralias($_POST['natport']) || is_portrange($_POST['natport'])) && !isset($_POST['nonat'])) {
$input_errors[] = gettext("A valid port must be supplied for the NAT port entry.");
}
if (($_POST['source_type'] != "any") && ($_POST['source_type'] != "(self)")) {
if ($_POST['source'] && !is_ipaddroralias($_POST['source']) && $_POST['source'] != "any") {
$input_errors[] = gettext("A valid source must be specified.");
}
}
if ($_POST['source_subnet'] && !is_numericint($_POST['source_subnet'])) {
$input_errors[] = gettext("A valid source bit count must be specified.");
}
if ($_POST['destination_type'] != "any") {
if ($_POST['destination'] && !is_ipaddroralias($_POST['destination'])) {
$input_errors[] = gettext("A valid destination must be specified.");
}
}
if ($_POST['destination_subnet'] && !is_numericint($_POST['destination_subnet'])) {
$input_errors[] = gettext("A valid destination bit count must be specified.");
}
if ($_POST['destination_type'] == "any") {
if ($_POST['destination_not']) {
$input_errors[] = gettext("Negating destination address of \"any\" is invalid.");
}
}
if ($_POST['target'] && !is_ipaddr($_POST['target']) && !is_subnet($_POST['target']) && !is_alias($_POST['target']) && !isset($_POST['nonat']) && !($_POST['target'] == "other-subnet")) {
$input_errors[] = gettext("A valid target IP address must be specified.");
}
if ($_POST['target'] == "other-subnet") {
if (!is_ipaddr($_POST['targetip'])) {
$input_errors[] = gettext("A valid target IP must be specified when using the 'Other Subnet' type.");
}
if (!is_numericint($_POST['targetip_subnet'])) {
$input_errors[] = gettext("A valid target bit count must be specified when using the 'Other Subnet' type.");
}
}
/* Verify Pool Options */
$poolopts = "";
$source_hash_key = "";
if ($_POST['poolopts']) {
if (is_subnet($_POST['target']) || ($_POST['target'] == "other-subnet")) {
$poolopts = $_POST['poolopts'];
} elseif (is_alias($_POST['target'])) {
if (substr($_POST['poolopts'], 0, 11) == "round-robin") {
$poolopts = $_POST['poolopts'];
} else {
$input_errors[] = gettext("Only Round Robin pool options may be chosen when selecting an alias.");
}
}
/* If specified, verify valid source-hash key or generate a valid key using md5 */
if ($_POST['source_hash_key']) {
if (substr($_POST['source_hash_key'],0,2) == "0x") {
if (ctype_xdigit(substr($_POST['source_hash_key'],2)) && strlen($_POST['source_hash_key']) == 34) {
$source_hash_key = $_POST['source_hash_key'];
} else {
$input_errors[] = gettext("Incorrect format for source-hash key, \"0x\" must be followed by exactly 32 hexadecimal characters.");
}
} else {
$source_hash_key = "0x".md5($_POST['source_hash_key']);
}
}
}
/* if user has selected any as source, set it here */
if ($_POST['source_type'] == "any") {
$osn = "any";
} else if ($_POST['source_type'] == "(self)") {
$osn = "(self)";
} else if (is_alias($_POST['source'])) {
$osn = $_POST['source'];
} else {
$osn = gen_subnet($_POST['source'], $_POST['source_subnet']) . "/" . $_POST['source_subnet'];
}
/* check for existing entries */
if ($_POST['destination_type'] == "any") {
$ext = "any";
} else if (is_alias($_POST['destination'])) {
$ext = $_POST['destination'];
} else {
$ext = gen_subnet($_POST['destination'], $_POST['destination_subnet']) . "/" . $_POST['destination_subnet'];
}
foreach ($a_out as $natent) {
if (isset($id) && ($a_out[$id]) && ($a_out[$id] === $natent)) {
continue;
}
if (!$natent['interface']) {
$natent['interface'] = "wan";
}
}
if (!$input_errors) {
$natent = array();
$natent['source']['network'] = $osn;
$natent['sourceport'] = ($protocol_uses_ports) ? $_POST['sourceport'] : "";
$natent['descr'] = $_POST['descr'];
$natent['target'] = (!isset($_POST['nonat'])) ? $_POST['target'] : "";
$natent['targetip'] = (!isset($_POST['nonat'])) ? $_POST['targetip'] : "";
$natent['targetip_subnet'] = (!isset($_POST['nonat'])) ? $_POST['targetip_subnet'] : "";
$natent['interface'] = $_POST['interface'];
$natent['poolopts'] = $poolopts;
$natent['source_hash_key'] = $source_hash_key;
/* static-port */
if (isset($_POST['staticnatport']) && $protocol_uses_ports && !isset($_POST['nonat'])) {
$natent['staticnatport'] = true;
} else {
unset($natent['staticnatport']);
}
if (isset($_POST['disabled'])) {
$natent['disabled'] = true;
} else {
unset($natent['disabled']);
}
/* if user has selected not nat, set it here */
if (isset($_POST['nonat'])) {
$natent['nonat'] = true;
} else {
unset($natent['nonat']);
}
if ($_POST['protocol'] && $_POST['protocol'] != "any") {
$natent['protocol'] = $_POST['protocol'];
} else {
unset($natent['protocol']);
}
if ($ext == "any") {
$natent['destination']['any'] = true;
} else {
$natent['destination']['address'] = $ext;
}
if ($_POST['natport'] != "" && $protocol_uses_ports && !isset($_POST['nonat'])) {
$natent['natport'] = $_POST['natport'];
} else {
unset($natent['natport']);
}
if ($_POST['dstport'] != "" && $protocol_uses_ports) {
$natent['dstport'] = $_POST['dstport'];
} else {
unset($natent['dstport']);
}
if ($_POST['nosync'] == "yes") {
$natent['nosync'] = true;
} else {
unset($natent['nosync']);
}
if (isset($_POST['destination_not']) && $ext != "any") {
$natent['destination']['not'] = true;
}
if (isset($a_out[$id]['created']) && is_array($a_out[$id]['created'])) {
$natent['created'] = $a_out[$id]['created'];
}
$natent['updated'] = make_config_revision_entry();
// Allow extending of the firewall edit page and include custom input validation
pfSense_handle_custom_code("/usr/local/pkg/firewall_aon/pre_write_config");
if (isset($id) && $a_out[$id]) {
$a_out[$id] = $natent;
} else {
$natent['created'] = make_config_revision_entry();
if (is_numeric($after)) {
array_splice($a_out, $after+1, 0, array($natent));
} else {
$a_out[] = $natent;
}
}
if (write_config()) {
mark_subsystem_dirty('natconf');
}
header("Location: firewall_nat_out.php");
exit;
}
}
$pgtitle = array(gettext("Firewall"), gettext("NAT"), gettext("Outbound"), gettext("Edit"));
$pglinks = array("", "firewall_nat.php", "firewall_nat_out.php", "@self");
include("head.inc");
function build_target_list() {
global $config, $sn, $a_aliases;
$list = array();
// Target list entries are made to start with the following characters:
// "" (blank) - the interface address of the selected interface
// S - a subnet
// I - an ordinary IP address
// H - a host alias
// O - other subnet
// The prefix letter makes it easy for the JavaScript to distinguish
// the type of entry based on the first letter of the value.
// The prefix letter is removed before saving in the config,
// and added back when reading from the config.
$list[""] = gettext('Interface Address');
//Temporary array so we can sort IPs
$templist = array();
if (is_array($config['virtualip']['vip'])) {
foreach ($config['virtualip']['vip'] as $sn) {
if (($sn['mode'] == "proxyarp" || $sn['mode'] == "other") && $sn['type'] == "network") {
$templist['S' . $sn['subnet'] . '/' . $sn['subnet_bits']] = gettext('Subnet: ') . $sn['subnet'] . '/' . $sn['subnet_bits'] . ' (' . $sn['descr'] . ')';
if (isset($sn['noexpand'])) {
continue;
}
$start = ip2long32(gen_subnet($sn['subnet'], $sn['subnet_bits']));
$end = ip2long32(gen_subnet_max($sn['subnet'], $sn['subnet_bits']));
$len = $end - $start;
for ($i = 0; $i <= $len; $i++) {
$snip = long2ip32($start+$i);
$templist['I' . $snip] = $snip . ' (' . $sn['descr'] . ')';
}
} else {
$templist['I' . $sn['subnet']] = $sn['subnet'] . ' (' . $sn['descr'] . ')';
}
}
}
asort($templist);
//Append sorted IP array onto main array
$list = array_merge($list, $templist);
unset($templist);
foreach ($a_aliases as $alias) {
if ($alias['type'] != "host") {
continue;
}
$list['H' . $alias['name']] = gettext('Host Alias: ') . $alias['name'] . ' (' . $alias['descr'] . ')';
}
$list['Oother-subnet'] = gettext('Other Subnet (Enter Below)');
return($list);
}
if ($input_errors) {
print_input_errors($input_errors);
}
$form = new Form();
$section = new Form_Section('Edit Advanced Outbound NAT Entry');
$section->addInput(new Form_Checkbox(
'disabled',
'Disabled',
'Disable this rule',
$pconfig['disabled']
));
$section->addInput(new Form_Checkbox(
'nonat',
'Do not NAT',
'Enabling this option will disable NAT for traffic matching this rule and stop processing Outbound NAT rules',
isset($pconfig['nonat'])
))->setHelp('In most cases this option is not required.');
$iflist = get_configured_interface_with_descr(false, true);
foreach ($iflist as $if => $ifdesc) {
if (have_ruleint_access($if)) {
$interfaces[$if] = $ifdesc;
}
}
if ($config['l2tp']['mode'] == "server") {
if (have_ruleint_access("l2tp")) {
$interfaces['l2tp'] = "L2TP VPN";
}
}
if (is_pppoe_server_enabled() && have_ruleint_access("pppoe")) {
$interfaces['pppoe'] = "PPPoE Server";
}
/* add ipsec interfaces */
if (ipsec_enabled() && have_ruleint_access("enc0")) {
$interfaces["enc0"] = "IPsec";
}
/* add openvpn/tun interfaces */
if ($config['openvpn']["openvpn-server"] || $config['openvpn']["openvpn-client"]) {
$interfaces["openvpn"] = "OpenVPN";
}
$section->addInput(new Form_Select(
'interface',
'*Interface',
$pconfig['interface'],
$interfaces
))->setHelp('The interface on which traffic is matched as it exits the firewall. In most cases this is "WAN" or another externally-connected interface.');
$protocols = "any TCP UDP TCP/UDP ICMP ESP AH GRE IPV6 IGMP carp pfsync";
$section->addInput(new Form_Select(
'protocol',
'*Protocol',
$pconfig['protocol'],
array_combine(explode(" ", strtolower($protocols)), explode(" ", $protocols))
))->setHelp('Choose which protocol this rule should match. In most cases "any" is specified.');
$group = new Form_Group('*Source');
$group->add(new Form_Select(
'source_type',
null,
(($pconfig['source'] == "any") || ($pconfig['source'] == "(self)")) ? $pconfig['source'] : "network",
array('any' => gettext('Any'), '(self)' => gettext('This Firewall (self)'), 'network' => gettext('Network'))
))->setHelp('Type')->setWidth('3');
$group->add(new Form_IpAddress(
'source',
null,
$pconfig['source'],
'ALIASV4V6'
))->addMask('source_subnet', $pconfig['source_subnet'])->setHelp('Source network for the outbound NAT mapping.');
$group->add(new Form_Input(
'sourceport',
null,
'text',
$pconfig['sourceport']
))->setHelp('Port or Range')->setWidth('2');
$section->add($group);
$group = new Form_Group('*Destination');
$group->add(new Form_Select(
'destination_type',
null,
$pconfig['destination'] == "any" ? "any":"network",
array('any' => gettext('Any'), 'network' => gettext('Network'))
))->setHelp('Type')->setWidth('3');
$group->add(new Form_IpAddress(
'destination',
null,
$pconfig['destination'] == "any" ? "":$pconfig['destination'],
'ALIASV4V6'
))->addMask('destination_subnet', $pconfig['destination_subnet'])->setHelp('Destination network for the outbound NAT mapping.');
$group->add(new Form_Input(
'dstport',
null,
'text',
$pconfig['dstport']
))->setHelp('Port or Range')->setWidth('2');
$section->add($group);
$section->addInput(new Form_Checkbox(
'destination_not',
null,
'Not',
$pconfig['destination_not']
))->setHelp('Invert the sense of the destination match.');
$form->add($section);
$section = new Form_Section('Translation');
$section->addClass('translation');
$section->addInput(new Form_Select(
'target',
'*Address',
$pconfig['target'],
build_target_list()
))->setHelp('Connections matching this rule will be mapped to the specified %1$sAddress%2$s.%3$s' .
'The %1$sAddress%2$s can be an Interface, a Host-type Alias, or a %4$sVirtual IP%5$s address.',
'', '', '
', '', '');
$section->addInput(new Form_IpAddress(
'targetip',
'Other subnet',
$pconfig['targetip']
))->addMask('targetip_subnet', $pconfig['targetip_subnet'])->setHelp(
'This subnet must be routed to the firewall or each address in the subnet must be defined in one or more %1$sVirtual IP%2$s addresses.',
'', '');
$section->addInput(new Form_Select(
'poolopts',
'Pool options',
$pconfig['poolopts'],
array(
'' => gettext('Default'),
'round-robin' => gettext('Round Robin'),
'round-robin sticky-address' => gettext('Round Robin with Sticky Address'),
'random' => gettext('Random'),
'random sticky-address' => gettext('Random with Sticky Address'),
'source-hash' => gettext('Source hash'),
'bitmask' => gettext('Bit mask')
)
))->setHelp('%s',
gettext('Only Round Robin types work with Host Aliases. Any type can be used with a Subnet.') .
'