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"); +?> + + + +
+
+
+
+
+ +
+

+
+ +
+ + + + + + + + + + + + (.*) 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]; + ?> + + + + + + + + + + + + + + +
+
+ + . +
+
+
+ +
+
+
+
+
+