From be8411be79de728da062944c755065d55c3cbbf3 Mon Sep 17 00:00:00 2001 From: Franco Fichtner Date: Wed, 23 Sep 2026 09:50:25 +0200 Subject: [PATCH] net/firewall-legacy: moved outbound NAT page from core --- net/firewall-legacy/Makefile | 3 +- net/firewall-legacy/pkg-descr | 12 + .../src/www/firewall_nat_out.php | 643 +++++++++++++ .../src/www/firewall_nat_out_edit.php | 892 ++++++++++++++++++ 4 files changed, 1548 insertions(+), 2 deletions(-) create mode 100644 net/firewall-legacy/src/www/firewall_nat_out.php create mode 100644 net/firewall-legacy/src/www/firewall_nat_out_edit.php diff --git a/net/firewall-legacy/Makefile b/net/firewall-legacy/Makefile index dd294f3e6..32caa2768 100644 --- a/net/firewall-legacy/Makefile +++ b/net/firewall-legacy/Makefile @@ -1,6 +1,5 @@ PLUGIN_NAME= firewall-legacy -PLUGIN_VERSION= 1.0 -PLUGIN_REVISION= 1 +PLUGIN_VERSION= 1.1 PLUGIN_COMMENT= Firewall rules legacy support PLUGIN_MAINTAINER= franco@opnsense.org diff --git a/net/firewall-legacy/pkg-descr b/net/firewall-legacy/pkg-descr index ed4c39b28..8e750a0a8 100644 --- a/net/firewall-legacy/pkg-descr +++ b/net/firewall-legacy/pkg-descr @@ -1,3 +1,15 @@ This plugin adds the legacy firewall rules configuration pages. Note that backend code still exists in the core and legacy rules can be migrated without this plugin installed. + + +Plugin Changelog +================ + +1.1 + +* Added outbound NAT page to the plugin + +1.0 + +* Initial version with firewall rules page diff --git a/net/firewall-legacy/src/www/firewall_nat_out.php b/net/firewall-legacy/src/www/firewall_nat_out.php new file mode 100644 index 000000000..425126148 --- /dev/null +++ b/net/firewall-legacy/src/www/firewall_nat_out.php @@ -0,0 +1,643 @@ + + * Copyright (C) 2003-2004 Manuel Kasper + * 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("filter.inc"); +require_once("interfaces.inc"); + +$a_out = &config_read_array('nat', 'outbound', 'rule'); +if (!isset($config['nat']['outbound']['mode'])) { + $config['nat']['outbound']['mode'] = "automatic"; +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $pconfig = $_POST; + if (isset($pconfig['id']) && isset($a_out[$pconfig['id']])) { + // id found and valid + $id = $pconfig['id']; + } + if (isset($pconfig['apply'])) { + if (write_config()) { + configd_run('filter reload'); + clear_subsystem_dirty('natconf'); + clear_subsystem_dirty('filter'); + } + } elseif (isset($pconfig['save']) && $pconfig['save'] == "Save") { + $mode = $config['nat']['outbound']['mode']; + $config['nat']['outbound']['mode'] = $pconfig['mode']; + if (write_config()) { + mark_subsystem_dirty('natconf'); + } + header(url_safe('Location: /firewall_nat_out.php')); + exit; + } elseif (isset($pconfig['act']) && $pconfig['act'] == 'del' && isset($id)) { + // delete single record + unset($a_out[$id]); + if (write_config()) { + mark_subsystem_dirty('natconf'); + } + header(url_safe('Location: /firewall_nat_out.php')); + exit; + } elseif (isset($pconfig['act']) && $pconfig['act'] == 'del_x' && isset($pconfig['rule']) && count($pconfig['rule']) > 0) { + /* delete selected rules */ + foreach ($pconfig['rule'] as $rulei) { + if (isset($a_out[$rulei])) { + unset($a_out[$rulei]); + } + } + if (write_config()) { + mark_subsystem_dirty('natconf'); + } + header(url_safe('Location: /firewall_nat_out.php')); + exit; + } elseif (isset($pconfig['act']) && in_array($pconfig['act'], array('toggle_enable', 'toggle_disable')) && isset($pconfig['rule']) && count($pconfig['rule']) > 0) { + foreach ($pconfig['rule'] as $rulei) { + $a_out[$rulei]['disabled'] = $pconfig['act'] == 'toggle_disable'; + } + if (write_config()) { + mark_subsystem_dirty('filter'); + } + header(url_safe('Location: /firewall_nat_out.php')); + exit; + } elseif (isset($pconfig['act']) && $pconfig['act'] == 'move' && isset($pconfig['rule']) && count($pconfig['rule']) > 0) { + // if rule not set/found, move to end + if (!isset($id)) { + $id = count($a_out); + } + $a_out = legacy_move_config_list_items($a_out, $id, $pconfig['rule']); + if (write_config()) { + mark_subsystem_dirty('natconf'); + } + header(url_safe('Location: /firewall_nat_out.php')); + exit; + } elseif (isset($pconfig['act']) && $pconfig['act'] == 'toggle' && isset($id)) { + // toggle item disabled / enabled + if(isset($a_out[$id]['disabled'])) { + unset($a_out[$id]['disabled']); + } else { + $a_out[$id]['disabled'] = true; + } + if (write_config('Firewall: NAT: Outbound, toggle NAT rule')) { + mark_subsystem_dirty('natconf'); + } + header(url_safe('Location: /firewall_nat_out.php')); + exit; + } +} + +$mode = $config['nat']['outbound']['mode']; + +$interface_names = []; +foreach (config_read_array('interfaces', false) as $intf => $intfdata) { + if (is_ipaddrv4($intfdata['ipaddr'] ?? 'none')) { + $interface_names[$intfdata['ipaddr']] = sprintf(gettext('%s address'), !empty($intfdata['descr']) ? $intfdata['descr'] : $intf ); + } +} + +include("head.inc"); + +?> + + + +
+
+
+".gettext("You must apply the changes in order for them to take effect.")); + print_info_box( + gettext("This legacy outbound NAT page is deprecated and will be replaced by the new Source NAT rules interface.") . "
" . + gettext("Use the migration assistant to export existing legacy outbound NAT rules and import them into the new interface.") . "

" . + '' . + ' ' . gettext("Open migration assistant") . + ' ' + ); +?> +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ /> + + + + /> + + +
+ /> + + + + /> + + +
+ +
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + " data-category="" ondblclick="document.location='firewall_nat_out_edit.php?id=';"> + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
  + + + + + + + + + +
+ + + + " class="fa fa-play "> + + " class="btn btn-default btn-xs "> + "> + + + + + + + +   + + + + + + + + + + + + + +
+
+
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + $networks): ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
  
  + "> +
  + "> +
+
+
+ +
+
+
+
+ + diff --git a/net/firewall-legacy/src/www/firewall_nat_out_edit.php b/net/firewall-legacy/src/www/firewall_nat_out_edit.php new file mode 100644 index 000000000..e0fe07869 --- /dev/null +++ b/net/firewall-legacy/src/www/firewall_nat_out_edit.php @@ -0,0 +1,892 @@ + + * Copyright (C) 2003-2004 Manuel Kasper + * 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("filter.inc"); + +/** + * return option array for valid translation networks + */ +function formTranslateAddresses() +{ + $retval = []; + + foreach (legacy_config_get_interfaces(['virtual' => false, 'enable' => true]) as $intf => $intfdata) { + $retval[$intf."ip"] = (!empty($intfdata['descr']) ? $intfdata['descr'] : $intf ) . " " . gettext("address"); + } + + foreach (config_read_array('virtualip', 'vip', false) as $sn) { + if (!empty($sn['noexpand'])) { + continue; + } + + if ($sn['mode'] == "proxyarp") { + $start = ip2long32(gen_subnet($sn['subnet'], $sn['subnet_bits'])); + $end = ip2long32(gen_subnet_max($sn['subnet'], $sn['subnet_bits'])); + $len = $end - $start; + $retval[$sn['subnet'].'/'.$sn['subnet_bits']] = htmlspecialchars("Subnet: {$sn['subnet']}/{$sn['subnet_bits']} ({$sn['descr']})"); + for ($i = 0; $i <= $len; $i++) { + $snip = long2ip32($start+$i); + $retval[$snip] = htmlspecialchars("{$snip} ({$sn['descr']})"); + } + } else { + $retval[$sn['subnet']] = htmlspecialchars("{$sn['subnet']} ({$sn['descr']})"); + } + } + + foreach (legacy_list_aliases("network") as $alias) { + if ($alias['type'] == "host") { + $retval[$alias['name']] = $alias['name']; + } + } + + return $retval; +} + +$a_out = &config_read_array('nat', 'outbound', 'rule'); + +if ($_SERVER['REQUEST_METHOD'] === 'GET') { + // input record id, if valid + if (isset($_GET['dup']) && isset($a_out[$_GET['dup']])) { + $configId = $_GET['dup']; + $after = $configId; + } elseif (isset($_GET['id']) && isset($a_out[$_GET['id']])) { + $id = $_GET['id']; + $configId = $id; + } + + // init form data + $pconfig = array(); + // set defaults + $pconfig['source'] = 'any'; + $pconfig['source_subnet'] = 24; + $pconfig['destination'] = "any"; + $pconfig['destination_subnet'] = 24; + $pconfig['interface'] = "wan"; + + if (isset($configId)) { + // load data from config + foreach (array('protocol','sourceport','dstport','natport','target','targetip' + ,'targetip_subnet','poolopts','poolopts_sourcehashkey','interface','descr','nonat','log' + ,'disabled','staticnatport','nosync','ipprotocol','tag','tagged', 'category') as $fieldname) { + if (isset($a_out[$configId][$fieldname])) { + $pconfig[$fieldname] = $a_out[$configId][$fieldname]; + } + } + + if (strpos($a_out[$configId]['source']['network'], "/") !== false) { + list($pconfig['source'],$pconfig['source_subnet']) = explode('/', $a_out[$configId]['source']['network']); + } else { + $pconfig['source'] = $a_out[$configId]['source']['network']; + } + $pconfig['source_not'] = !empty($a_out[$configId]['source']['not']); + + if (!is_numeric($pconfig['source_subnet'])) { + $pconfig['source_subnet'] = 32; + } + address_to_pconfig($a_out[$configId]['destination'], $pconfig['destination'], + $pconfig['destination_subnet'], $pconfig['destination_not'], + $none, $none); + } + + // initialize unused elements + foreach (array('protocol','sourceport','dstport','natport','target','targetip', + 'targetip_subnet','poolopts','poolopts_sourcehashkey','interface','descr','nonat','tag','tagged', + 'disabled','staticnatport','nosync','source','source_subnet','ipprotocol') as $fieldname) { + if (!isset($pconfig[$fieldname])) { + $pconfig[$fieldname] = null; + } + } + if (empty($pconfig['ipprotocol'])) { + if (strpos($pconfig['source'].$pconfig['destination'].$pconfig['targetip'], ":") !== false) { + $pconfig['ipprotocol'] = 'inet6'; + } else { + $pconfig['ipprotocol'] = 'inet'; + } + } + if (empty($pconfig['targetip'])) { + $pconfig['targetip'] = $pconfig['target']; + } + $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($pconfig['id']) && isset($a_out[$pconfig['id']])) { + $id = $pconfig['id']; + } + if (isset($pconfig['after']) && isset($a_out[$pconfig['after']])) { + $after = $pconfig['after']; + } + + /* input validation */ + $reqdfields = explode(" ", "interface protocol source destination"); + $reqdfieldsn = array(gettext("Interface"),gettext("Protocol"),gettext("Source"),gettext("Destination")); + + do_input_validation($pconfig, $reqdfields, $reqdfieldsn, $input_errors); + + if (in_array($pconfig['protocol'], explode(" ", "any tcp udp tcp/udp"))) { + if(!empty($pconfig['sourceport']) && !is_portrange($pconfig['sourceport']) && !is_portoralias($pconfig['sourceport'])) { + $input_errors[] = gettext("You must supply either a valid port or port alias for the source port entry."); + } + if(!empty($pconfig['dstport']) && !is_portrange($pconfig['dstport']) && !is_portoralias($pconfig['dstport'])) { + $input_errors[] = gettext("You must supply either a valid port or port alias for the destination port entry."); + } + if (!empty($pconfig['natport']) && !(is_port($pconfig['natport']) || is_portrange($pconfig['natport'])) && empty($pconfig['nonat'])) { + $input_errors[] = gettext("You must supply a valid port for the NAT port entry."); + } + } + + if (!is_specialnet($pconfig['source']) && !is_ipaddroralias($pconfig['source'])) { + $input_errors[] = sprintf(gettext("%s is not a valid source IP address or alias."), $pconfig['source']); + } + + if (!empty($pconfig['source_subnet']) && !is_numericint($pconfig['source_subnet'])) { + $input_errors[] = gettext("A valid source bit count must be specified."); + } + if ($pconfig['source'] == "any" && !empty($pconfig['source_not'])) { + $input_errors[] = gettext("Negating source address of \"any\" is invalid."); + } + if (!is_specialnet($pconfig['destination']) && !is_ipaddroralias($pconfig['destination'])) { + $input_errors[] = gettext("A valid destination must be specified."); + } + if (!empty($pconfig['destination_subnet']) && !is_numericint($pconfig['destination_subnet'])) { + $input_errors[] = gettext("A valid destination bit count must be specified."); + } + if ($pconfig['destination'] == "any" && !empty($pconfig['destination_not'])) { + $input_errors[] = gettext("Negating destination address of \"any\" is invalid."); + } + + if (!empty($pconfig['targetip']) && !is_ipaddr($pconfig['targetip']) && !is_subnet($pconfig['targetip']) + && !is_specialnet($pconfig['targetip']) && !is_alias($pconfig['targetip']) && empty($pconfig['nonat'])) { + $input_errors[] = gettext("A valid target IP address must be specified."); + } + /* Verify Pool Options */ + if (!empty($pconfig['targetip']) && is_alias($pconfig['targetip']) && !empty($pconfig['poolopts']) && substr($pconfig['poolopts'], 0, 11) != 'round-robin') { + $input_errors[] = gettext("Only Round Robin pool options may be chosen when selecting an alias."); + } + /* Verify Source Hash Key if provided */ + if (!empty($pconfig['poolopts_sourcehashkey'])){ + if (empty($pconfig['poolopts']) || $pconfig['poolopts'] != 'source-hash') { + $input_errors[] = gettext("Source Hash Key is only valid for Source Hash type"); + } + if (substr($pconfig['poolopts_sourcehashkey'], 0, 2) != "0x" || !ctype_xdigit(substr($pconfig['poolopts_sourcehashkey'], 2, 32)) ){ + $input_errors[] = gettext("Source Hash Key must be 0x followed by 32 hexadecimal digits"); + } + } + // validate ipv4/v6, addresses should use selected address family + foreach (array('source', 'destination', 'targetip') as $fieldname) { + if (is_ipaddrv6($pconfig[$fieldname]) && $pconfig['ipprotocol'] != 'inet6') { + $input_errors[] = sprintf(gettext("%s is not a valid IPv4 address."), $pconfig[$fieldname]); + } + if (is_ipaddrv4($pconfig[$fieldname]) && $pconfig['ipprotocol'] != 'inet') { + $input_errors[] = sprintf(gettext("%s is not a valid IPv6 address."), $pconfig[$fieldname]); + } + } + + if (count($input_errors) == 0) { + $natent = array(); + $natent['source'] = array(); + $natent['destination'] = array(); + $natent['descr'] = $pconfig['descr']; + $natent['category'] = !empty($pconfig['category']) ? implode(",", $pconfig['category']) : null; + $natent['interface'] = $pconfig['interface']; + $natent['tag'] = $pconfig['tag']; + $natent['tagged'] = $pconfig['tagged']; + $natent['poolopts'] = $pconfig['poolopts']; + $natent['poolopts_sourcehashkey'] = $pconfig['poolopts_sourcehashkey']; + $natent['ipprotocol'] = $pconfig['ipprotocol']; + + if (isset($a_out[$id]['created']) && is_array($a_out[$id]['created']) ){ + $natent['created'] = $a_out[$id]['created']; + } + + // target ip/net + if (empty($pconfig['targetip'])) { + // empty target "Interface address" + $natent['target'] = $pconfig['targetip']; + $natent['targetip_subnet'] = 0; + } elseif (!array_key_exists($pconfig['targetip'], formTranslateAddresses())) { + // a bit vague behaviour in "target" and "targetip", if a custom net is given + // the backend code wants target to be filled with "other-subnet". + // if any other known net is given, target is used to provide the actual address.... + // -- can't remove this behaviour now without breaking old config, so let's reimplement + $natent['target'] = 'other-subnet'; + $natent['targetip'] = trim($pconfig['targetip']); + $natent['targetip_subnet'] = $pconfig['targetip_subnet']; + } else { + $natent['target'] = $pconfig['targetip']; + } + + // handle fields containing port numbers + if (in_array($pconfig['protocol'], explode(" ", "any tcp udp tcp/udp"))) { + if (isset($pconfig['staticnatport']) && empty($pconfig['nonat'])) { + $natent['staticnatport'] = true; + } + $natent['sourceport'] = trim($pconfig['sourceport']); + if (!empty($pconfig['natport']) && empty($pconfig['nonat'])) { + $natent['natport'] = trim($pconfig['natport']); + } + if (!empty($pconfig['dstport'])) { + $natent['dstport'] = trim($pconfig['dstport']); + } + } else { + $natent['sourceport'] = ""; + } + + if (!empty($pconfig['protocol']) && $pconfig['protocol'] != "any") { + $natent['protocol'] = $pconfig['protocol']; + } + + /* parse source entry */ + if($pconfig['source'] == "any") { + $natent['source']['network'] = "any"; + } else if($pconfig['source'] == "(self)") { + $natent['source']['network'] = "(self)"; + } else if(is_alias($pconfig['source']) || is_specialnet($pconfig['source'])) { + $natent['source']['network'] = trim($pconfig['source']); + } else { + if (is_ipaddrv6($pconfig['source'])) { + $natent['source']['network'] = gen_subnetv6(trim($pconfig['source']), $pconfig['source_subnet']) . "/" . $pconfig['source_subnet']; + } else { + $natent['source']['network'] = gen_subnet(trim($pconfig['source']), $pconfig['source_subnet']) . "/" . $pconfig['source_subnet']; + } + } + + // destination address + if ($pconfig['destination'] == "any") { + $natent['destination']['any'] = true; + } elseif (is_alias($pconfig['destination']) || is_specialnet($pconfig['destination'])){ + $natent['destination']['network'] = trim($pconfig['destination']) ; + } else { + if (is_ipaddrv6($pconfig['destination'])) { + $natent['destination']['address'] = gen_subnetv6(trim($pconfig['destination']), $pconfig['destination_subnet']) . "/" . $pconfig['destination_subnet']; + } else { + $natent['destination']['address'] = gen_subnet(trim($pconfig['destination']), $pconfig['destination_subnet']) . "/" . $pconfig['destination_subnet']; + } + } + + // boolean fields + if(!empty($pconfig['disabled'])) { + $natent['disabled'] = true; + } + if(!empty($pconfig['nonat'])) { + $natent['nonat'] = true; + } + if (!empty($pconfig['log'])) { + $natent['log'] = true; + } + + if(isset($pconfig['nosync'] ) && $pconfig['nosync'] == "yes") { + $natent['nosync'] = true; + } + if (isset($pconfig['destination_not']) && $pconfig['destination'] != "any") { + $natent['destination']['not'] = true; + } + if (isset($pconfig['source_not']) && $pconfig['source'] != "any") { + $natent['source']['not'] = true; + } + + $natent['updated'] = make_config_revision_entry(); + if (isset($id)) { + $a_out[$id] = $natent; + } else { + $natent['created'] = make_config_revision_entry(); + if (isset($after)) { + array_splice($a_out, $after+1, 0, array($natent)); + } else { + $a_out[] = $natent; + } + } + OPNsense\Core\Config::getInstance()->fromArray($config); + $catmdl = new OPNsense\Firewall\Category(); + if ($catmdl->sync()) { + $catmdl->serializeToConfig(); + $config = OPNsense\Core\Config::getInstance()->toArray(listtags()); + } + if (write_config()) { + mark_subsystem_dirty('natconf'); + } + header(url_safe('Location: /firewall_nat_out.php')); + exit; + } +} + +legacy_html_escape_form_data($pconfig); + +include("head.inc"); + +?> + + + + + + + +
+
+
+ 0) print_input_errors($input_errors); ?> +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + +
+
+ /> + + +
+ /> + +
+ + +
+ + +
+ + +
+ /> + +
+ + + + +
+ +
+ + + + + + +
+ "/> + + +
+ +
+ + + + + + + + + +
+ +
+ +
+ +
+ /> + +
+ + + + +
+ +
+ + + + + + +
+ "/> + + +
+ +
+ + + + + + + + + +
+ +
+ +
+ +
+ + + + +
+ +
+ + + + + + +
+ "/> + + +
+ +
+ /> + + +
+ + +
+ > +
+ + + + + + + + + +
+ + +
+ +
+
+ + +
+ + +
+ /> + +
+ + +
+ + +
 
+ () +
+ () +
  + + + + + + " /> +
+
+
+
+
+
+
+