diff --git a/net/upnp/Makefile b/net/upnp/Makefile
new file mode 100644
index 000000000..027f6b11f
--- /dev/null
+++ b/net/upnp/Makefile
@@ -0,0 +1,8 @@
+PLUGIN_NAME= upnp
+PLUGIN_VERSION= 0.1
+PLUGIN_PRIVATE= yes
+PLUGIN_DEPENDS= miniupnpd
+PLUGIN_COMMENT= Universal Plug and Play Service
+PLUGIN_MAINTAINER= franco@opnsense.org
+
+.include "../../Mk/plugins.mk"
diff --git a/net/upnp/src/etc/inc/plugins.inc.d/miniupnpd.inc b/net/upnp/src/etc/inc/plugins.inc.d/miniupnpd.inc
new file mode 100644
index 000000000..2eade24ce
--- /dev/null
+++ b/net/upnp/src/etc/inc/plugins.inc.d/miniupnpd.inc
@@ -0,0 +1,225 @@
+registerAnchor('miniupnpd', 'rdr');
+ $fw->registerAnchor('miniupnpd', 'fw');
+}
+
+function miniupnpd_services()
+{
+ $services = array();
+
+ if (!miniupnpd_enabled()) {
+ return $services;
+ }
+
+ $pconfig = array();
+ $pconfig['name'] = 'miniupnpd';
+ $pconfig['description'] = gettext('Univeral Plug and Play');
+ $pconfig['php']['restart'] = array('miniupnpd_stop', 'miniupnpd_start');
+ $pconfig['php']['start'] = array('miniupnpd_start');
+ $pconfig['php']['stop'] = array('miniupnpd_stop');
+ $pconfig['pidfile'] = '/var/run/miniupnpd.pid';
+ $services[] = $pconfig;
+
+ return $services;
+}
+
+function miniupnpd_start()
+{
+ if (!miniupnpd_enabled()) {
+ return;
+ }
+
+ if (isvalidpid('/var/run/miniupnpd.pid')) {
+ return;
+ }
+
+ mwexec_bg('/usr/local/sbin/miniupnpd -f /var/etc/miniupnpd.conf -P /var/run/miniupnpd.pid');
+}
+
+function miniupnpd_stop()
+{
+ killbypid('/var/run/miniupnpd.pid', 'TERM', true);
+ mwexec('/sbin/pfctl -aminiupnpd -Fr 2>&1 >/dev/null');
+ mwexec('/sbin/pfctl -aminiupnpd -Fn 2>&1 >/dev/null');
+}
+
+function miniupnpd_configure()
+{
+ return array('miniupnpd_configure_do');
+}
+
+function miniupnpd_uuid()
+{
+ /* md5 hash of wan mac */
+ $uuid = md5(get_interface_mac(get_real_interface("wan")));
+ /* put uuid in correct format 8-4-4-4-12 */
+ return substr($uuid,0,8).'-'.substr($uuid,9,4).'-'.substr($uuid,13,4).'-'.substr($uuid,17,4).'-'.substr($uuid,21,12);
+}
+
+function miniupnpd_configure_do($verbose = false)
+{
+ global $config;
+
+ miniupnpd_stop();
+
+ if (!miniupnpd_enabled()) {
+ return;
+ }
+
+ if ($verbose) {
+ echo 'Starting UPnP service...';
+ flush();
+ }
+
+ $upnp_config = $config['installedpackages']['miniupnpd']['config'][0];
+
+ $ext_ifname = get_real_interface($upnp_config['ext_iface']);
+ if ($ext_ifname == $upnp_config['ext_iface']) {
+ if ($verbose) {
+ echo "failed.\n";
+ }
+ return;
+ }
+
+ $config_text = "ext_ifname={$ext_ifname}\n";
+ $config_text .= "port=2189\n";
+
+ $ifaces_active = '';
+
+ /* since config is written before this file invoked we don't need to read post data */
+ if (!empty($upnp_config['iface_array'])) {
+ foreach(explode(',', $upnp_config['iface_array']) as $iface) {
+ /* Setting the same internal and external interface is not allowed. */
+ if ($iface == $upnp_config['ext_iface']) {
+ continue;
+ }
+ $if = get_real_interface($iface);
+ /* above function returns iface if fail */
+ if ($if!=$iface) {
+ $addr = find_interface_ip($if);
+ $bits = find_interface_subnet($if);
+ /* check that the interface has an ip address before adding parameters */
+ if (is_ipaddr($addr)) {
+ $config_text .= "listening_ip={$if}\n";
+ if (!$ifaces_active) {
+ $webgui_ip = $addr;
+ $ifaces_active = $iface;
+ } else {
+ $ifaces_active .= ", {$iface}";
+ }
+ } else {
+ log_error("miniupnpd: Interface {$iface} has no ip address, ignoring");
+ }
+ } else {
+ log_error("miniupnpd: Could not resolve real interface for {$iface}");
+ }
+ }
+
+ if (!empty($ifaces_active)) {
+ /* override wan ip address, common for carp, etc */
+ if (!empty($upnp_config['overridewanip'])) {
+ $config_text .= "ext_ip={$upnp_config['overridewanip']}\n";
+ }
+ /* set upload and download bitrates */
+ if (!empty($upnp_config['download']) && !empty($upnp_config['upload'])) {
+ $download = $upnp_config['download']*1000;
+ $upload = $upnp_config['upload']*1000;
+ $config_text .= "bitrate_down={$download}\n";
+ $config_text .= "bitrate_up={$upload}\n";
+ }
+
+ $config_text .= "secure_mode=yes\n";
+
+ /* enable logging of packets handled by miniupnpd rules */
+ if (!empty($upnp_config['logpackets'])) {
+ $config_text .= "packet_log=yes\n";
+ }
+
+ /* enable system uptime instead of miniupnpd uptime */
+ if (!empty($upnp_config['sysuptime'])) {
+ $config_text .= "system_uptime=yes\n";
+ }
+
+ /* set webgui url */
+ if (!empty($config['system']['webgui']['protocol'])) {
+ $config_text .= "presentation_url={$config['system']['webgui']['protocol']}://{$webgui_ip}";
+ if (!empty($config['system']['webgui']['port'])) {
+ $config_text .= ":{$config['system']['webgui']['port']}";
+ }
+ $config_text .= "/\n";
+ }
+
+ /* set uuid and serial */
+ $config_text .= "uuid=".miniupnpd_uuid()."\n";
+ $config_text .= "serial=".strtoupper(substr(miniupnpd_uuid(),0,8))."\n";
+
+ /* set model number */
+ $config_text .= "model_number=".file_get_contents("/usr/local/opnsense/version/opnsense")."\n";
+
+ /* upnp access restrictions */
+ for ($i=1; $i<=4; $i++) {
+ if ($upnp_config["permuser{$i}"]) {
+ $config_text .= "{$upnp_config["permuser{$i}"]}\n";
+ }
+ }
+
+ if (!empty($upnp_config['permdefault'])) {
+ $config_text .= "deny 0-65535 0.0.0.0/0 0-65535\n";
+ }
+
+ /* Allow UPnP or NAT-PMP as requested */
+ $config_text .= "enable_upnp=" . ( $upnp_config['enable_upnp'] ? "yes\n" : "no\n" );
+ $config_text .= "enable_natpmp=" . ( $upnp_config['enable_natpmp'] ? "yes\n" : "no\n" );
+
+ /* write out the configuration */
+ file_put_contents('/var/etc/miniupnpd.conf', $config_text);
+
+ log_error("miniupnpd: Starting service on interface: {$ifaces_active}");
+ miniupnpd_start();
+ }
+ }
+
+ if ($verbose) {
+ echo "done.\n";
+ }
+}
diff --git a/net/upnp/src/opnsense/mvc/app/models/OPNsense/UPnP/ACL/ACL.xml b/net/upnp/src/opnsense/mvc/app/models/OPNsense/UPnP/ACL/ACL.xml
new file mode 100644
index 000000000..cc93dde4a
--- /dev/null
+++ b/net/upnp/src/opnsense/mvc/app/models/OPNsense/UPnP/ACL/ACL.xml
@@ -0,0 +1,14 @@
+
+
+ Service: Universal Plug and Play
+
+ services_upnp.php*
+
+
+
+ Status: Universal Plug and Play
+
+ status_upnp.php*
+
+
+
diff --git a/net/upnp/src/opnsense/mvc/app/models/OPNsense/UPnP/Menu/Menu.xml b/net/upnp/src/opnsense/mvc/app/models/OPNsense/UPnP/Menu/Menu.xml
new file mode 100644
index 000000000..0dfae6b0f
--- /dev/null
+++ b/net/upnp/src/opnsense/mvc/app/models/OPNsense/UPnP/Menu/Menu.xml
@@ -0,0 +1,10 @@
+
diff --git a/net/upnp/src/www/services_upnp.php b/net/upnp/src/www/services_upnp.php
new file mode 100644
index 000000000..131a81037
--- /dev/null
+++ b/net/upnp/src/www/services_upnp.php
@@ -0,0 +1,378 @@
+
+ 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("services.inc");
+require_once("filter.inc");
+require_once("system.inc");
+require_once("plugins.inc.d/miniupnpd.inc");
+
+function upnp_validate_ip($ip) {
+ /* validate cidr */
+ $ip_array = array();
+ $ip_array = explode('/', $ip);
+ if (count($ip_array) == 2) {
+ if ($ip_array[1] < 1 || $ip_array[1] > 32) {
+ return false;
+ }
+ } elseif (count($ip_array) != 1) {
+ return false;
+ }
+
+ /* validate ip */
+ if (!is_ipaddr($ip_array[0])) {
+ return false;
+ }
+ return true;
+}
+
+function upnp_validate_port($port) {
+ foreach (explode('-', $port) as $sub) {
+ if ($sub < 0 || $sub > 65535 || !is_numeric($sub)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+if ($_SERVER['REQUEST_METHOD'] === 'GET') {
+ $pconfig = array();
+
+ $copy_fields = array('enable', 'enable_upnp', 'enable_natpmp', 'ext_iface', 'iface_array', 'download',
+ 'upload', 'overridewanip', 'logpackets', 'sysuptime', 'permdefault', 'permuser1',
+ 'permuser2', 'permuser3', 'permuser4');
+
+ foreach ($copy_fields as $fieldname) {
+ if (isset($config['installedpackages']['miniupnpd']['config'][0][$fieldname])) {
+ $pconfig[$fieldname] = $config['installedpackages']['miniupnpd']['config'][0][$fieldname];
+ }
+ }
+ // parse array
+ $pconfig['iface_array'] = explode(',', $pconfig['iface_array']);
+} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $input_errors = array();
+ $pconfig = $_POST;
+
+ // validate form data
+ if (!empty($pconfig['enable']) && (empty($pconfig['enable_upnp']) && empty($pconfig['enable_natpmp']))) {
+ $input_errors[] = gettext('At least one of \'UPnP\' or \'NAT-PMP\' must be allowed');
+ }
+ if (!empty($pconfig['iface_array'])) {
+ foreach($pconfig['iface_array'] as $iface) {
+ if ($iface == 'wan') {
+ $input_errors[] = gettext('It is a security risk to specify WAN as an internal interface.');
+ } elseif ($iface == $pconfig['ext_iface']) {
+ $input_errors[] = gettext('You cannot select the external interface as an internal interface.');
+ }
+ }
+ } else {
+ $input_errors[] = gettext('You must specify at least one internal interface.');
+ }
+ if (!empty($pconfig['overridewanip']) && !is_ipaddr($pconfig['overridewanip'])) {
+ $input_errors[] = gettext('You must specify a valid ip address in the \'Override WAN address\' field');
+ }
+ if ((!empty($pconfig['download']) && empty($pconfig['upload'])) || (!empty($pconfig['upload']) && empty($pconfig['download']))) {
+ $input_errors[] = gettext('You must fill in both \'Maximum Download Speed\' and \'Maximum Upload Speed\' fields');
+ }
+ if (!empty($pconfig['download']) && ($pconfig['download'] <= 0 || !is_numeric($pconfig['download']))) {
+ $input_errors[] = gettext('You must specify a value greater than 0 in the \'Maximum Download Speed\' field');
+ }
+ if (!empty($pconfig['upload']) && ($pconfig['upload'] <= 0 || !is_numeric($pconfig['upload']))) {
+ $input_errors[] = gettext('You must specify a value greater than 0 in the \'Maximum Upload Speed\' field');
+ }
+
+ /* user permissions validation */
+ for($i=1; $i<=4; $i++) {
+ if (!empty($pconfig["permuser{$i}"])) {
+ $perm = explode(' ',$pconfig["permuser{$i}"]);
+ /* should explode to 4 args */
+ if (count($perm) != 4) {
+ $input_errors[] = sprintf(gettext("You must follow the specified format in the 'User specified permissions %s' field"), $i);
+ } else {
+ /* must with allow or deny */
+ if (!($perm[0] == 'allow' || $perm[0] == 'deny')) {
+ $input_errors[] = sprintf(gettext("You must begin with allow or deny in the 'User specified permissions %s' field"), $i);
+ }
+ /* verify port or port range */
+ if (!upnp_validate_port($perm[1]) || !upnp_validate_port($perm[3])) {
+ $input_errors[] = sprintf(gettext("You must specify a port or port range between 0 and 65535 in the 'User specified permissions %s' field"), $i);
+ }
+ /* verify ip address */
+ if (!upnp_validate_ip($perm[2])) {
+ $input_errors[] = sprintf(gettext("You must specify a valid ip address in the 'User specified permissions %s' field"), $i);
+ }
+ }
+ }
+ }
+
+ if (count($input_errors) == 0) {
+ // save form data
+ $upnp = array();
+ // boolean types
+ foreach (array('enable', 'enable_upnp', 'enable_natpmp', 'logpackets', 'sysuptime', 'permdefault') as $fieldname) {
+ $upnp[$fieldname] = !empty($pconfig[$fieldname]);
+ }
+ // text field types
+ foreach (array('ext_iface', 'download', 'upload', 'overridewanip', 'permuser1',
+ 'permuser2', 'permuser3', 'permuser4') as $fieldname) {
+ $upnp[$fieldname] = $pconfig[$fieldname];
+ }
+ // array types
+ $upnp['iface_array'] = implode(',', $pconfig['iface_array']);
+ // sync to config
+ $config['installedpackages']['miniupnpd']['config'] = $upnp;
+
+ write_config('Modified Universal Plug and Play settings');
+ miniupnpd_configure_do();
+ filter_configure();
+ header(url_safe('Location: /services_upnp.php'));
+ exit;
+ }
+}
+
+
+$service_hook = 'miniupnpd';
+legacy_html_escape_form_data($pconfig);
+include("head.inc");
+?>
+
+
+
+
+
+ 0) print_input_errors($input_errors); ?>
+
+
+
+
+
diff --git a/net/upnp/src/www/status_upnp.php b/net/upnp/src/www/status_upnp.php
new file mode 100644
index 000000000..19975583c
--- /dev/null
+++ b/net/upnp/src/www/status_upnp.php
@@ -0,0 +1,117 @@
+.
+ 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("services.inc");
+require_once("interfaces.inc");
+require_once("plugins.inc.d/miniupnpd.inc");
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ if (!empty($_POST['clear'])) {
+ miniupnpd_stop();
+ miniupnpd_start();
+ header(url_safe('Location: /status_upnp.php'));
+ exit;
+ }
+}
+
+$rdr_entries = array();
+exec("/sbin/pfctl -aminiupnpd -sn", $rdr_entries, $pf_ret);
+
+$service_hook = 'miniupnpd';
+include("head.inc");
+?>
+
+
+
+
+
+
+
+
+
+
+ = gettext('UPnP is currently disabled.') ?>
+
+
+
+
+
+
+ | =gettext("Port");?> |
+ =gettext("Protocol");?> |
+ =gettext("Internal IP");?> |
+ =gettext("Int. Port");?> |
+ =gettext("Description");?> |
+
+
+
+ (.*) port (.*)/", $rdr_entry, $matches)) {
+ continue;
+ }
+ $rdr_proto = $matches[2];
+ $rdr_port = $matches[5];
+ $rdr_label =$matches[6];
+ $rdr_ip = $matches[7];
+ $rdr_iport = $matches[8];
+ ?>
+
+ | =$rdr_port;?> |
+ =$rdr_proto;?> |
+ =$rdr_ip;?> |
+ =$rdr_iport;?> |
+ =$rdr_label;?> |
+
+
+
+
+
+ |
+
+ |
+
+
+
+
+
+
+
+
+
+
+