summaryrefslogtreecommitdiffstats
path: root/src/etc/inc
diff options
context:
space:
mode:
Diffstat (limited to 'src/etc/inc')
-rw-r--r--src/etc/inc/CHAP.inc463
-rw-r--r--src/etc/inc/IPv6.inc1110
-rw-r--r--src/etc/inc/PEAR.inc1103
-rw-r--r--src/etc/inc/auth.inc1627
-rw-r--r--src/etc/inc/authgui.inc335
-rw-r--r--src/etc/inc/basic_sasl_client.inc63
-rw-r--r--src/etc/inc/captiveportal.inc2409
-rw-r--r--src/etc/inc/certs.inc867
-rw-r--r--src/etc/inc/config.console.inc560
-rw-r--r--src/etc/inc/config.gui.inc96
-rw-r--r--src/etc/inc/config.inc226
-rw-r--r--src/etc/inc/config.lib.inc1020
-rw-r--r--src/etc/inc/cram_md5_sasl_client.inc67
-rw-r--r--src/etc/inc/crypt.inc101
-rw-r--r--src/etc/inc/digest_sasl_client.inc135
-rw-r--r--src/etc/inc/dot.hushlogin0
-rw-r--r--src/etc/inc/dyndns.class1634
-rw-r--r--src/etc/inc/easyrule.inc495
-rw-r--r--src/etc/inc/filter.inc4228
-rw-r--r--src/etc/inc/filter_log.inc441
-rw-r--r--src/etc/inc/functions.inc158
-rw-r--r--src/etc/inc/globals.inc195
-rw-r--r--src/etc/inc/gmirror.inc342
-rw-r--r--src/etc/inc/growl.class102
-rw-r--r--src/etc/inc/gwlb.inc1252
-rw-r--r--src/etc/inc/interfaces.inc5814
-rw-r--r--src/etc/inc/ipsec.attributes.php200
-rwxr-xr-xsrc/etc/inc/ipsec.auth-user.php169
-rw-r--r--src/etc/inc/ipsec.inc777
-rw-r--r--src/etc/inc/itemid.inc108
-rw-r--r--src/etc/inc/led.inc356
-rw-r--r--src/etc/inc/login_sasl_client.inc69
-rw-r--r--src/etc/inc/meta.inc215
-rw-r--r--src/etc/inc/notices.inc453
-rw-r--r--src/etc/inc/ntlm_sasl_client.inc180
-rw-r--r--src/etc/inc/openvpn.attributes.php203
-rw-r--r--src/etc/inc/openvpn.auth-user.php213
-rw-r--r--src/etc/inc/openvpn.inc1589
-rw-r--r--src/etc/inc/openvpn.tls-verify.php97
-rw-r--r--src/etc/inc/pfsense-utils.inc3211
-rw-r--r--src/etc/inc/pkg-utils.inc953
-rw-r--r--src/etc/inc/plain_sasl_client.inc99
-rw-r--r--src/etc/inc/priv.defs.inc1403
-rw-r--r--src/etc/inc/priv.inc337
-rw-r--r--src/etc/inc/priv/user.priv.inc74
-rw-r--r--src/etc/inc/r53.class754
-rw-r--r--src/etc/inc/radius.inc1208
-rw-r--r--src/etc/inc/rrd.inc989
-rw-r--r--src/etc/inc/sasl.inc422
-rw-r--r--src/etc/inc/service-utils.inc749
-rw-r--r--src/etc/inc/services.inc2541
-rw-r--r--src/etc/inc/shaper.inc4969
-rw-r--r--src/etc/inc/simplepie/LICENSE.txt26
-rw-r--r--src/etc/inc/simplepie/simplepie.inc13672
-rw-r--r--src/etc/inc/smtp.inc862
-rw-r--r--src/etc/inc/system.inc2258
-rw-r--r--src/etc/inc/unbound.inc717
-rw-r--r--src/etc/inc/upgrade_config.inc3825
-rw-r--r--src/etc/inc/util.inc2259
-rw-r--r--src/etc/inc/uuid.php327
-rw-r--r--src/etc/inc/voucher.inc785
-rw-r--r--src/etc/inc/vpn.inc2056
-rw-r--r--src/etc/inc/vslb.inc564
-rw-r--r--src/etc/inc/wizardapp.inc668
-rw-r--r--src/etc/inc/xmlparse.inc334
-rw-r--r--src/etc/inc/xmlparse_attr.inc237
-rw-r--r--src/etc/inc/xmlreader.inc277
-rw-r--r--src/etc/inc/xmlrpc.inc148
-rw-r--r--src/etc/inc/xmlrpc_client.inc2060
-rw-r--r--src/etc/inc/xmlrpc_server.inc678
-rw-r--r--src/etc/inc/zeromq.inc340
71 files changed, 79274 insertions, 0 deletions
diff --git a/src/etc/inc/CHAP.inc b/src/etc/inc/CHAP.inc
new file mode 100644
index 0000000..6eb22f7
--- /dev/null
+++ b/src/etc/inc/CHAP.inc
@@ -0,0 +1,463 @@
+<?php
+/*
+Copyright (c) 2002-2010, Michael Bretterklieber <michael@bretterklieber.com>
+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.
+3. The names of the authors may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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.
+
+This code cannot simply be copied and put under the GNU Public License or
+any other GPL-like (LGPL, GPL2) License.
+
+ $Id: CHAP.php 302857 2010-08-28 21:12:59Z mbretter $
+*/
+
+require_once 'PEAR.inc';
+
+/**
+* Classes for generating packets for various CHAP Protocols:
+* CHAP-MD5: RFC1994
+* MS-CHAPv1: RFC2433
+* MS-CHAPv2: RFC2759
+*
+* @package Crypt_CHAP
+* @author Michael Bretterklieber <michael@bretterklieber.com>
+* @access public
+* @version $Revision: 302857 $
+*/
+
+/**
+ * class Crypt_CHAP
+ *
+ * Abstract base class for CHAP
+ *
+ * @package Crypt_CHAP
+ */
+class Crypt_CHAP extends PEAR
+{
+ /**
+ * Random binary challenge
+ * @var string
+ */
+ var $challenge = null;
+
+ /**
+ * Binary response
+ * @var string
+ */
+ var $response = null;
+
+ /**
+ * User password
+ * @var string
+ */
+ var $password = null;
+
+ /**
+ * Id of the authentication request. Should incremented after every request.
+ * @var integer
+ */
+ var $chapid = 1;
+
+ /**
+ * Constructor
+ *
+ * Generates a random challenge
+ * @return void
+ */
+ function Crypt_CHAP()
+ {
+ $this->PEAR();
+ $this->generateChallenge();
+ }
+
+ /**
+ * Generates a random binary challenge
+ *
+ * @param string $varname Name of the property
+ * @param integer $size Size of the challenge in Bytes
+ * @return void
+ */
+ function generateChallenge($varname = 'challenge', $size = 8)
+ {
+ $this->$varname = '';
+ for ($i = 0; $i < $size; $i++) {
+ $this->$varname .= pack('C', 1 + mt_rand() % 255);
+ }
+ return $this->$varname;
+ }
+
+ /**
+ * Generates the response. Overwrite this.
+ *
+ * @return void
+ */
+ function challengeResponse()
+ {
+ }
+
+}
+
+/**
+ * class Crypt_CHAP_MD5
+ *
+ * Generate CHAP-MD5 Packets
+ *
+ * @package Crypt_CHAP
+ */
+class Crypt_CHAP_MD5 extends Crypt_CHAP
+{
+
+ /**
+ * Generates the response.
+ *
+ * CHAP-MD5 uses MD5-Hash for generating the response. The Hash consists
+ * of the chapid, the plaintext password and the challenge.
+ *
+ * @return string
+ */
+ function challengeResponse()
+ {
+ return pack('H*', md5(pack('C', $this->chapid) . $this->password . $this->challenge));
+ }
+}
+
+/**
+ * class Crypt_CHAP_MSv1
+ *
+ * Generate MS-CHAPv1 Packets. MS-CHAP doesen't use the plaintext password, it uses the
+ * NT-HASH wich is stored in the SAM-Database or in the smbpasswd, if you are using samba.
+ * The NT-HASH is MD4(str2unicode(plaintextpass)).
+ * You need the hash extension for this class.
+ *
+ * @package Crypt_CHAP
+ */
+class Crypt_CHAP_MSv1 extends Crypt_CHAP
+{
+ /**
+ * Wether using deprecated LM-Responses or not.
+ * 0 = use LM-Response, 1 = use NT-Response
+ * @var bool
+ */
+ var $flags = 1;
+
+ /**
+ * Constructor
+ *
+ * Loads the hash extension
+ * @return void
+ */
+ function Crypt_CHAP_MSv1()
+ {
+ $this->Crypt_CHAP();
+ $this->loadExtension('hash');
+ }
+
+ /**
+ * Generates the NT-HASH from the given plaintext password.
+ *
+ * @access public
+ * @return string
+ */
+ function ntPasswordHash($password = null)
+ {
+ if (isset($password)) {
+ return pack('H*',hash('md4', $this->str2unicode($password)));
+ } else {
+ return pack('H*',hash('md4', $this->str2unicode($this->password)));
+ }
+ }
+
+ /**
+ * Converts ascii to unicode.
+ *
+ * @access public
+ * @return string
+ */
+ function str2unicode($str)
+ {
+ $uni = '';
+ $str = (string) $str;
+ for ($i = 0; $i < strlen($str); $i++) {
+ $a = ord($str{$i}) << 8;
+ $uni .= sprintf("%X", $a);
+ }
+ return pack('H*', $uni);
+ }
+
+ /**
+ * Generates the NT-Response.
+ *
+ * @access public
+ * @return string
+ */
+ function challengeResponse()
+ {
+ return $this->_challengeResponse();
+ }
+
+ /**
+ * Generates the NT-Response.
+ *
+ * @access public
+ * @return string
+ */
+ function ntChallengeResponse()
+ {
+ return $this->_challengeResponse(false);
+ }
+
+ /**
+ * Generates the LAN-Manager-Response.
+ *
+ * @access public
+ * @return string
+ */
+ function lmChallengeResponse()
+ {
+ return $this->_challengeResponse(true);
+ }
+
+ /**
+ * Generates the response.
+ *
+ * Generates the response using DES.
+ *
+ * @param bool $lm wether generating LAN-Manager-Response
+ * @access private
+ * @return string
+ */
+ function _challengeResponse($lm = false)
+ {
+ if ($lm) {
+ $hash = $this->lmPasswordHash();
+ } else {
+ $hash = $this->ntPasswordHash();
+ }
+
+ while (strlen($hash) < 21) {
+ $hash .= "\0";
+ }
+
+ $td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_ECB, '');
+ $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
+ $key = $this->_desAddParity(substr($hash, 0, 7));
+ mcrypt_generic_init($td, $key, $iv);
+ $resp1 = mcrypt_generic($td, $this->challenge);
+ mcrypt_generic_deinit($td);
+
+ $key = $this->_desAddParity(substr($hash, 7, 7));
+ mcrypt_generic_init($td, $key, $iv);
+ $resp2 = mcrypt_generic($td, $this->challenge);
+ mcrypt_generic_deinit($td);
+
+ $key = $this->_desAddParity(substr($hash, 14, 7));
+ mcrypt_generic_init($td, $key, $iv);
+ $resp3 = mcrypt_generic($td, $this->challenge);
+ mcrypt_generic_deinit($td);
+ mcrypt_module_close($td);
+
+ return $resp1 . $resp2 . $resp3;
+ }
+
+ /**
+ * Generates the LAN-Manager-HASH from the given plaintext password.
+ *
+ * @access public
+ * @return string
+ */
+ function lmPasswordHash($password = null)
+ {
+ $plain = isset($password) ? $password : $this->password;
+
+ $plain = substr(strtoupper($plain), 0, 14);
+ while (strlen($plain) < 14) {
+ $plain .= "\0";
+ }
+
+ return $this->_desHash(substr($plain, 0, 7)) . $this->_desHash(substr($plain, 7, 7));
+ }
+
+ /**
+ * Generates an irreversible HASH.
+ *
+ * @access private
+ * @return string
+ */
+ function _desHash($plain)
+ {
+ $key = $this->_desAddParity($plain);
+ $td = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_ECB, '');
+ $iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
+ mcrypt_generic_init($td, $key, $iv);
+ $hash = mcrypt_generic($td, 'KGS!@#$%');
+ mcrypt_generic_deinit($td);
+ mcrypt_module_close($td);
+ return $hash;
+ }
+
+ /**
+ * Adds the parity bit to the given DES key.
+ *
+ * @access private
+ * @param string $key 7-Bytes Key without parity
+ * @return string
+ */
+ function _desAddParity($key)
+ {
+ static $odd_parity = array(
+ 1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14,
+ 16, 16, 19, 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31,
+ 32, 32, 35, 35, 37, 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47,
+ 49, 49, 50, 50, 52, 52, 55, 55, 56, 56, 59, 59, 61, 61, 62, 62,
+ 64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74, 76, 76, 79, 79,
+ 81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91, 91, 93, 93, 94, 94,
+ 97, 97, 98, 98,100,100,103,103,104,104,107,107,109,109,110,110,
+ 112,112,115,115,117,117,118,118,121,121,122,122,124,124,127,127,
+ 128,128,131,131,133,133,134,134,137,137,138,138,140,140,143,143,
+ 145,145,146,146,148,148,151,151,152,152,155,155,157,157,158,158,
+ 161,161,162,162,164,164,167,167,168,168,171,171,173,173,174,174,
+ 176,176,179,179,181,181,182,182,185,185,186,186,188,188,191,191,
+ 193,193,194,194,196,196,199,199,200,200,203,203,205,205,206,206,
+ 208,208,211,211,213,213,214,214,217,217,218,218,220,220,223,223,
+ 224,224,227,227,229,229,230,230,233,233,234,234,236,236,239,239,
+ 241,241,242,242,244,244,247,247,248,248,251,251,253,253,254,254);
+
+ $bin = '';
+ for ($i = 0; $i < strlen($key); $i++) {
+ $bin .= sprintf('%08s', decbin(ord($key{$i})));
+ }
+
+ $str1 = explode('-', substr(chunk_split($bin, 7, '-'), 0, -1));
+ $x = '';
+ foreach($str1 as $s) {
+ $x .= sprintf('%02s', dechex($odd_parity[bindec($s . '0')]));
+ }
+
+ return pack('H*', $x);
+
+ }
+
+ /**
+ * Generates the response-packet.
+ *
+ * @param bool $lm wether including LAN-Manager-Response
+ * @access private
+ * @return string
+ */
+ function response($lm = false)
+ {
+ $ntresp = $this->ntChallengeResponse();
+ if ($lm) {
+ $lmresp = $this->lmChallengeResponse();
+ } else {
+ $lmresp = str_repeat ("\0", 24);
+ }
+
+ // Response: LM Response, NT Response, flags (0 = use LM Response, 1 = use NT Response)
+ return $lmresp . $ntresp . pack('C', !$lm);
+ }
+}
+
+/**
+ * class Crypt_CHAP_MSv2
+ *
+ * Generate MS-CHAPv2 Packets. This version of MS-CHAP uses a 16 Bytes authenticator
+ * challenge and a 16 Bytes peer Challenge. LAN-Manager responses no longer exists
+ * in this version. The challenge is already a SHA1 challenge hash of both challenges
+ * and of the username.
+ *
+ * @package Crypt_CHAP
+ */
+class Crypt_CHAP_MSv2 extends Crypt_CHAP_MSv1
+{
+ /**
+ * The username
+ * @var string
+ */
+ var $username = null;
+
+ /**
+ * The 16 Bytes random binary peer challenge
+ * @var string
+ */
+ var $peerChallenge = null;
+
+ /**
+ * The 16 Bytes random binary authenticator challenge
+ * @var string
+ */
+ var $authChallenge = null;
+
+ /**
+ * Constructor
+ *
+ * Generates the 16 Bytes peer and authentication challenge
+ * @return void
+ */
+ function Crypt_CHAP_MSv2()
+ {
+ $this->Crypt_CHAP_MSv1();
+ $this->generateChallenge('peerChallenge', 16);
+ $this->generateChallenge('authChallenge', 16);
+ }
+
+ /**
+ * Generates a hash from the NT-HASH.
+ *
+ * @access public
+ * @param string $nthash The NT-HASH
+ * @return string
+ */
+ function ntPasswordHashHash($nthash)
+ {
+ return pack('H*',hash('md4', $nthash));
+ }
+
+ /**
+ * Generates the challenge hash from the peer and the authenticator challenge and
+ * the username. SHA1 is used for this, but only the first 8 Bytes are used.
+ *
+ * @access public
+ * @return string
+ */
+ function challengeHash()
+ {
+ return substr(pack('H*',hash('sha1', $this->peerChallenge . $this->authChallenge . $this->username)), 0, 8);
+ }
+
+ /**
+ * Generates the response.
+ *
+ * @access public
+ * @return string
+ */
+ function challengeResponse()
+ {
+ $this->challenge = $this->challengeHash();
+ return $this->_challengeResponse();
+ }
+}
+
+
+?>
diff --git a/src/etc/inc/IPv6.inc b/src/etc/inc/IPv6.inc
new file mode 100644
index 0000000..faacb8d
--- /dev/null
+++ b/src/etc/inc/IPv6.inc
@@ -0,0 +1,1110 @@
+<?php
+
+/*
+ pfSense_MODULE: utils
+*/
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * This file contains the implementation of the Net_IPv6 class
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to the New BSD license, that is
+ * available through the world-wide-web at
+ * http://www.opensource.org/licenses/bsd-license.php
+ * If you did not receive a copy of the new BSDlicense and are unable
+ * to obtain it through the world-wide-web, please send a note to
+ * license@php.net so we can mail you a copy immediately
+ *
+ * @category Net
+ * @package Net_IPv6
+ * @author Alexander Merz <alexander.merz@web.de>
+ * @copyright 2003-2005 The PHP Group
+ * @license BSD License http://www.opensource.org/licenses/bsd-license.php
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/Net_IPv6
+ */
+
+// {{{ constants
+
+/**
+ * Error message if netmask bits was not found
+ * @see isInNetmask
+ */
+define("NET_IPV6_NO_NETMASK_MSG", "Netmask length not found");
+
+/**
+ * Error code if netmask bits was not found
+ * @see isInNetmask
+ */
+define("NET_IPV6_NO_NETMASK", 10);
+
+/**
+ * Address Type: Unassigned (RFC 1884, Section 2.3)
+ * @see getAddressType()
+ */
+define("NET_IPV6_UNASSIGNED", 1);
+
+/**
+ * Address Type: Reserved (RFC 1884, Section 2.3)
+ * @see getAddressType()
+ */
+define("NET_IPV6_RESERVED", 11);
+
+/**
+ * Address Type: Reserved for NSAP Allocation (RFC 1884, Section 2.3)
+ * @see getAddressType()
+ */
+define("NET_IPV6_RESERVED_NSAP", 12);
+
+/**
+ * Address Type: Reserved for IPX Allocation (RFC 1884, Section 2.3)
+ * @see getAddressType()
+ */
+define("NET_IPV6_RESERVED_IPX", 13);
+
+/**
+ * Address Type: Reserved for Geographic-Based Unicast Addresses
+ * (RFC 1884, Section 2.3)
+ * @see getAddressType()
+ */
+define("NET_IPV6_RESERVED_UNICAST_GEOGRAPHIC", 14);
+
+/**
+ * Address Type: Provider-Based Unicast Address (RFC 1884, Section 2.3)
+ * @see getAddressType()
+ */
+define("NET_IPV6_UNICAST_PROVIDER", 22);
+
+/**
+ * Address Type: Multicast Addresses (RFC 1884, Section 2.3)
+ * @see getAddressType()
+ */
+define("NET_IPV6_MULTICAST", 31);
+
+/**
+ * Address Type: Link Local Use Addresses (RFC 1884, Section 2.3)
+ * @see getAddressType()
+ */
+define("NET_IPV6_LOCAL_LINK", 42);
+
+/**
+ * Address Type: Link Local Use Addresses (RFC 1884, Section 2.3)
+ * @see getAddressType()
+ */
+define("NET_IPV6_LOCAL_SITE", 43);
+
+/**
+ * Address Type: Address range to embedded IPv4 ip in an IPv6 address (RFC 4291, Section 2.5.5)
+ * @see getAddressType()
+ */
+define("NET_IPV6_IPV4MAPPING", 51);
+
+/**
+ * Address Type: Unspecified (RFC 4291, Section 2.5.2)
+ * @see getAddressType()
+ */
+define("NET_IPV6_UNSPECIFIED", 52);
+
+/**
+ * Address Type: Unspecified (RFC 4291, Section 2.5.3)
+ * @see getAddressType()
+ */
+define("NET_IPV6_LOOPBACK", 53);
+
+/**
+ * Address Type: address can not assigned to a specific type
+ * @see getAddressType()
+ */
+define("NET_IPV6_UNKNOWN_TYPE", 1001);
+
+// }}}
+// {{{ Net_IPv6
+
+/**
+ * Class to validate and to work with IPv6 addresses.
+ *
+ * @category Net
+ * @package Net_IPv6
+ * @author Alexander Merz <alexander.merz@web.de>
+ * @author <elfrink at introweb dot nl>
+ * @author Josh Peck <jmp at joshpeck dot org>
+ * @copyright 2003-2010 The PHP Group
+ * @license BSD License http://www.opensource.org/licenses/bsd-license.php
+ * @version Release: 1.1.0RC5
+ * @link http://pear.php.net/package/Net_IPv6
+ */
+class Net_IPv6
+{
+
+ // {{{ separate()
+ /**
+ * Separates an IPv6 address into the address and a prefix length part
+ *
+ * @param String $ip the (compressed) IP as Hex representation
+ *
+ * @return Array the first element is the IP, the second the prefix length
+ * @since 1.2.0
+ * @access public
+ * @static
+ */
+ static function separate($ip)
+ {
+
+ $addr = $ip;
+ $spec = '';
+
+ if(false === strrpos($ip, '/')) {
+
+ return array($addr, $spec);
+
+ }
+
+ $elements = explode('/', $ip);
+
+ if(2 == count($elements)) {
+
+ $addr = $elements[0];
+ $spec = $elements[1];
+
+ }
+
+ return array($addr, $spec);
+
+ }
+ // }}}
+
+ // {{{ removeNetmaskSpec()
+
+ /**
+ * Removes a possible existing prefix length/ netmask specification at an IP address.
+ *
+ * @param String $ip the (compressed) IP as Hex representation
+ *
+ * @return String the IP without netmask length
+ * @since 1.1.0
+ * @access public
+ * @static
+ */
+ static function removeNetmaskSpec($ip)
+ {
+
+ $elements = Net_IPv6::separate($ip);
+
+ return $elements[0];
+
+ }
+ // }}}
+ // {{{ removePrefixLength()
+
+ /**
+ * Tests for a prefix length specification in the address
+ * and removes the prefix length, if exists
+ *
+ * The method is technically identical to removeNetmaskSpec() and
+ * will be dropped in a future release.
+ *
+ * @param String $ip a valid ipv6 address
+ *
+ * @return String the address without a prefix length
+ * @access public
+ * @static
+ * @see removeNetmaskSpec()
+ * @deprecated
+ */
+ static function removePrefixLength($ip)
+ {
+ $pos = strrpos($ip, '/');
+
+ if (false !== $pos) {
+
+ return substr($ip, 0, $pos);
+
+ }
+
+ return $ip;
+ }
+
+ // }}}
+ // {{{ getNetmaskSpec()
+
+ /**
+ * Returns a possible existing prefix length/netmask specification on an IP address.
+ *
+ * @param String $ip the (compressed) IP as Hex representation
+ *
+ * @return String the netmask spec
+ * @since 1.1.0
+ * @access public
+ * @static
+ */
+ static function getNetmaskSpec($ip)
+ {
+
+ $elements = Net_IPv6::separate($ip);
+
+ return $elements[1];
+
+ }
+
+ // }}}
+ // {{{ getPrefixLength()
+
+ /**
+ * Tests for a prefix length specification in the address
+ * and returns the prefix length, if exists
+ *
+ * The method is technically identical to getNetmaskSpec() and
+ * will be dropped in a future release.
+ *
+ * @param String $ip a valid ipv6 address
+ *
+ * @return Mixed the prefix as String or false, if no prefix was found
+ * @access public
+ * @static
+ * @deprecated
+ */
+ static function getPrefixLength($ip)
+ {
+ if (preg_match("/^([0-9a-fA-F:]{2,39})\/(\d{1,3})*$/",
+ $ip, $matches)) {
+
+ return $matches[2];
+
+ } else {
+
+ return false;
+
+ }
+
+ }
+
+ // }}}
+ // {{{ getNetmask()
+
+ /**
+ * Calculates the network prefix based on the netmask bits.
+ *
+ * @param String $ip the (compressed) IP in Hex format
+ * @param int $bits if the number of netmask bits is not part of the IP
+ * you must provide the number of bits
+ *
+ * @return String the network prefix
+ * @since 1.1.0
+ * @access public
+ * @static
+ */
+ static function getNetmask($ip, $bits = null)
+ {
+ if (null==$bits) {
+
+ $elements = explode('/', $ip);
+
+ if (2 == count($elements)) {
+
+ $addr = $elements[0];
+ $bits = $elements[1];
+
+ } else {
+
+ include_once 'PEAR.inc';
+
+ return PEAR::raiseError(NET_IPV6_NO_NETMASK_MSG,
+ NET_IPV6_NO_NETMASK);
+ }
+
+ } else {
+
+ $addr = $ip;
+
+ }
+
+ $addr = Net_IPv6::uncompress($addr);
+ $binNetmask = str_repeat('1', $bits).str_repeat('0', 128 - $bits);
+
+ return Net_IPv6::_bin2Ip(Net_IPv6::_ip2Bin($addr) & $binNetmask);
+ }
+
+ // }}}
+ // {{{ isInNetmask()
+
+ /**
+ * Checks if an (compressed) IP is in a specific address space.
+ *
+ * If the IP does not contain the number of netmask bits (F8000::FFFF/16)
+ * then you have to use the $bits parameter.
+ *
+ * @param String $ip the IP to check (eg. F800::FFFF)
+ * @param String $netmask the netmask (eg F800::)
+ * @param int $bits the number of netmask bits to compare,
+ * if not given in $ip
+ *
+ * @return boolean true if $ip is in the netmask
+ * @since 1.1.0
+ * @access public
+ * @static
+ */
+ static function isInNetmask($ip, $netmask, $bits=null)
+ {
+ // try to get the bit count
+
+ if (null == $bits) {
+
+ $elements = explode('/', $ip);
+
+ if (2 == count($elements)) {
+
+ $ip = $elements[0];
+ $bits = $elements[1];
+
+ } else if (null == $bits) {
+
+ $elements = explode('/', $netmask);
+
+ if (2 == count($elements)) {
+
+ $netmask = $elements[0];
+ $bits = $elements[1];
+
+ }
+
+ if (null == $bits) {
+
+ include_once 'PEAR.inc';
+ return PEAR::raiseError(NET_IPV6_NO_NETMASK_MSG,
+ NET_IPV6_NO_NETMASK);
+
+ }
+
+ }
+
+ }
+
+ $binIp = Net_IPv6::_ip2Bin(Net_IPv6::removeNetmaskSpec($ip));
+ $binNetmask = Net_IPv6::_ip2Bin(Net_IPv6::removeNetmaskSpec($netmask));
+
+ if (null != $bits
+ && "" != $bits
+ && 0 == strncmp($binNetmask, $binIp, $bits)) {
+
+ return true;
+
+ }
+
+ return false;
+ }
+
+ // }}}
+ // {{{ getAddressType()
+
+ /**
+ * Returns the type of an IPv6 address.
+ *
+ * RFC 2373, Section 2.3 describes several types of addresses in
+ * the IPv6 address space.
+ * Several address types are markers for reserved spaces and as
+ * a consequence are subject to change.
+ *
+ * @param String $ip the IP address in Hex format,
+ * compressed IPs are allowed
+ *
+ * @return int one of the address type constants
+ * @access public
+ * @since 1.1.0
+ * @static
+ *
+ * @see NET_IPV6_UNASSIGNED
+ * @see NET_IPV6_RESERVED
+ * @see NET_IPV6_RESERVED_NSAP
+ * @see NET_IPV6_RESERVED_IPX
+ * @see NET_IPV6_RESERVED_UNICAST_GEOGRAPHIC
+ * @see NET_IPV6_UNICAST_PROVIDER
+ * @see NET_IPV6_MULTICAST
+ * @see NET_IPV6_LOCAL_LINK
+ * @see NET_IPV6_LOCAL_SITE
+ * @see NET_IPV6_IPV4MAPPING
+ * @see NET_IPV6_UNSPECIFIED
+ * @see NET_IPV6_LOOPBACK
+ * @see NET_IPV6_UNKNOWN_TYPE
+ */
+ static function getAddressType($ip)
+ {
+ $ip = Net_IPv6::removeNetmaskSpec($ip);
+ $binip = Net_IPv6::_ip2Bin($ip);
+
+ if(0 == strncmp(str_repeat('0', 128), $binip, 128)) { // ::/128
+
+ return NET_IPV6_UNSPECIFIED;
+
+ } else if(0 == strncmp(str_repeat('0', 127).'1', $binip, 128)) { // ::/128
+
+ return NET_IPV6_LOOPBACK;
+
+ } else if (0 == strncmp(str_repeat('0', 80).str_repeat('1', 16), $binip, 96)) { // ::ffff/96
+
+ return NET_IPV6_IPV4MAPPING;
+
+ } else if (0 == strncmp('1111111010', $binip, 10)) {
+
+ return NET_IPV6_LOCAL_LINK;
+
+ } else if (0 == strncmp('1111111011', $binip, 10)) {
+
+ return NET_IPV6_LOCAL_SITE;
+
+ } else if (0 == strncmp('111111100', $binip, 9)) {
+
+ return NET_IPV6_UNASSIGNED;
+
+ } else if (0 == strncmp('11111111', $binip, 8)) {
+
+ return NET_IPV6_MULTICAST;
+
+ } else if (0 == strncmp('00000000', $binip, 8)) {
+
+ return NET_IPV6_RESERVED;
+
+ } else if (0 == strncmp('00000001', $binip, 8)
+ || 0 == strncmp('1111110', $binip, 7)) {
+
+ return NET_IPV6_UNASSIGNED;
+
+ } else if (0 == strncmp('0000001', $binip, 7)) {
+
+ return NET_IPV6_RESERVED_NSAP;
+
+ } else if (0 == strncmp('0000010', $binip, 7)) {
+
+ return NET_IPV6_RESERVED_IPX;
+
+ } else if (0 == strncmp('0000011', $binip, 7) ||
+ 0 == strncmp('111110', $binip, 6) ||
+ 0 == strncmp('11110', $binip, 5) ||
+ 0 == strncmp('00001', $binip, 5) ||
+ 0 == strncmp('1110', $binip, 4) ||
+ 0 == strncmp('0001', $binip, 4) ||
+ 0 == strncmp('001', $binip, 3) ||
+ 0 == strncmp('011', $binip, 3) ||
+ 0 == strncmp('101', $binip, 3) ||
+ 0 == strncmp('110', $binip, 3)) {
+
+ return NET_IPV6_UNASSIGNED;
+
+ } else if (0 == strncmp('010', $binip, 3)) {
+
+ return NET_IPV6_UNICAST_PROVIDER;
+
+ } else if (0 == strncmp('100', $binip, 3)) {
+
+ return NET_IPV6_RESERVED_UNICAST_GEOGRAPHIC;
+
+ }
+
+ return NET_IPV6_UNKNOWN_TYPE;
+ }
+
+ // }}}
+ // {{{ Uncompress()
+
+ /**
+ * Uncompresses an IPv6 address
+ *
+ * RFC 2373 allows you to compress zeros in an address to '::'. This
+ * function expects a valid IPv6 address and expands the '::' to
+ * the required zeros.
+ *
+ * Example: FF01::101 -> FF01:0:0:0:0:0:0:101
+ * ::1 -> 0:0:0:0:0:0:0:1
+ *
+ * Note: You can also pass an invalid IPv6 address (usually as part of the process
+ * of validation by checkIPv6, which will validate the return string).
+ * This function will insert the "0" between "::" even if this results in too many
+ * numbers in total. It is NOT the purpose of this function to validate your input.
+ *
+ * Example of calling with invalid input: 1::2:3:4:5:6:7:8:9 -> 1:0:2:3:4:5:6:7:8:9
+ *
+ * @param String $ip a (possibly) valid IPv6-address (hex format)
+ * @param Boolean $leadingZeros if true, leading zeros are added to each
+ * block of the address
+ * (FF01::101 ->
+ * FF01:0000:0000:0000:0000:0000:0000:0101)
+ *
+ * @return String the uncompressed IPv6-address (hex format)
+ * @access public
+ * @see Compress()
+ * @static
+ * @author Pascal Uhlmann
+ */
+ static function uncompress($ip, $leadingZeros = false)
+ {
+
+ $prefix = Net_IPv6::getPrefixLength($ip);
+
+ if (false === $prefix) {
+
+ $prefix = '';
+
+ } else {
+
+ $ip = Net_IPv6::removePrefixLength($ip);
+ $prefix = '/'.$prefix;
+
+ }
+
+ $netmask = Net_IPv6::getNetmaskSpec($ip);
+ $uip = Net_IPv6::removeNetmaskSpec($ip);
+
+ $c1 = -1;
+ $c2 = -1;
+
+ if (false !== strpos($uip, '::') ) {
+
+ list($ip1, $ip2) = explode('::', $uip);
+
+ if ("" == $ip1) {
+
+ $c1 = -1;
+
+ } else {
+
+ $pos = 0;
+
+ if (0 < ($pos = substr_count($ip1, ':'))) {
+
+ $c1 = $pos;
+
+ } else {
+
+ $c1 = 0;
+
+ }
+ }
+ if ("" == $ip2) {
+
+ $c2 = -1;
+
+ } else {
+
+ $pos = 0;
+
+ if (0 < ($pos = substr_count($ip2, ':'))) {
+
+ $c2 = $pos;
+
+ } else {
+
+ $c2 = 0;
+
+ }
+
+ }
+
+ if (strstr($ip2, '.')) {
+
+ $c2++;
+
+ }
+ if (-1 == $c1 && -1 == $c2) { // ::
+
+ $uip = "0:0:0:0:0:0:0:0";
+
+ } else if (-1 == $c1) { // ::xxx
+
+ $fill = str_repeat('0:', 7-$c2);
+ $uip = str_replace('::', $fill, $uip);
+
+ } else if (-1 == $c2) { // xxx::
+
+ $fill = str_repeat(':0', 7-$c1);
+ $uip = str_replace('::', $fill, $uip);
+
+ } else { // xxx::xxx
+
+ $fill = str_repeat(':0:', max(1, 6-$c2-$c1));
+ $uip = str_replace('::', $fill, $uip);
+ $uip = str_replace('::', ':', $uip);
+
+ }
+ }
+
+ if(true == $leadingZeros) {
+
+ $uipT = array();
+ $uiparts = explode(':', $uip);
+
+ foreach($uiparts as $p) {
+
+ $uipT[] = sprintf('%04s', $p);
+
+ }
+
+ $uip = implode(':', $uipT);
+ }
+
+ if ('' != $netmask) {
+
+ $uip = $uip.'/'.$netmask;
+
+ }
+
+ return $uip.$prefix;
+ }
+
+ // }}}
+ // {{{ Compress()
+
+ /**
+ * Compresses an IPv6 address
+ *
+ * RFC 2373 allows you to compress zeros in an address to '::'. This
+ * function expects a valid IPv6 address and compresses successive zeros
+ * to '::'
+ *
+ * Example: FF01:0:0:0:0:0:0:101 -> FF01::101
+ * 0:0:0:0:0:0:0:1 -> ::1
+ *
+ * When $ip is an already compressed address and $force is false, the method returns
+ * the value as is, even if the address can be compressed further.
+ *
+ * Example: FF01::0:1 -> FF01::0:1
+ *
+ * To enforce maximum compression, you can set the second argument $force to true.
+ *
+ * Example: FF01::0:1 -> FF01::1
+ *
+ * @param String $ip a valid IPv6-address (hex format)
+ * @param boolean $force if true the address will be compressed as best as possible (since 1.2.0)
+ *
+ * @return String the compressed IPv6-address (hex format)
+ * @access public
+ * @see Uncompress()
+ * @static
+ * @author elfrink at introweb dot nl
+ */
+ static function compress($ip, $force = false)
+ {
+
+ if(false !== strpos($ip, '::')) { // its already compressed
+
+ if(true == $force) {
+
+ $ip = Net_IPv6::uncompress($ip);
+
+ } else {
+
+ return $ip;
+
+ }
+
+ }
+
+ $prefix = Net_IPv6::getPrefixLength($ip);
+
+ if (false === $prefix) {
+
+ $prefix = '';
+
+ } else {
+
+ $ip = Net_IPv6::removePrefixLength($ip);
+ $prefix = '/'.$prefix;
+
+ }
+
+ $netmask = Net_IPv6::getNetmaskSpec($ip);
+ $ip = Net_IPv6::removeNetmaskSpec($ip);
+
+ $ipp = explode(':', $ip);
+
+ for ($i = 0; $i < count($ipp); $i++) {
+
+ $ipp[$i] = dechex(hexdec($ipp[$i]));
+
+ }
+
+ $cip = ':' . join(':', $ipp) . ':';
+
+ preg_match_all("/(:0)(:0)+/", $cip, $zeros);
+
+ if (count($zeros[0]) > 0) {
+
+ $match = '';
+
+ foreach ($zeros[0] as $zero) {
+
+ if (strlen($zero) > strlen($match)) {
+
+ $match = $zero;
+
+ }
+ }
+
+ $cip = preg_replace('/' . $match . '/', ':', $cip, 1);
+
+ }
+
+ $cip = preg_replace('/((^:)|(:$))/', '', $cip);
+ $cip = preg_replace('/((^:)|(:$))/', '::', $cip);
+
+ if (empty($cip)) {
+
+ $cip = "::";
+
+ }
+
+ if ('' != $netmask) {
+
+ $cip = $cip.'/'.$netmask;
+
+ }
+
+ return $cip.$prefix;
+
+ }
+
+ // }}}
+ // {{{ recommendedFormat()
+ /**
+ * Represent IPv6 address in RFC5952 format.
+ *
+ * @param String $ip a valid IPv6-address (hex format)
+ *
+ * @return String the recommended representation of IPv6-address (hex format)
+ * @access public
+ * @see compress()
+ * @static
+ * @author koyama at hoge dot org
+ * @todo This method may become a part of compress() in a further releases
+ */
+ static function recommendedFormat($ip)
+ {
+ $compressed = self::compress($ip, true);
+ // RFC5952 4.2.2
+ // The symbol "::" MUST NOT be used to shorten just one
+ // 16-bit 0 field.
+ if ((substr_count($compressed, ':') == 7) &&
+ (strpos($compressed, '::') !== false)) {
+ $compressed = str_replace('::', ':0:', $compressed);
+ }
+ return $compressed;
+ }
+ // }}}
+
+ // {{{ isCompressible()
+
+ /**
+ * Checks, if an IPv6 address can be compressed
+ *
+ * @param String $ip a valid IPv6 address
+ *
+ * @return Boolean true, if address can be compressed
+ *
+ * @access public
+ * @since 1.2.0b
+ * @static
+ * @author Manuel Schmitt
+ */
+ static function isCompressible($ip)
+ {
+
+ return (bool)($ip != Net_IPv6::compress($address));
+
+ }
+
+ // }}}
+ // {{{ SplitV64()
+
+ /**
+ * Splits an IPv6 address into the IPv6 and a possible IPv4 part
+ *
+ * RFC 2373 allows you to note the last two parts of an IPv6 address as
+ * an IPv4 compatible address
+ *
+ * Example: 0:0:0:0:0:0:13.1.68.3
+ * 0:0:0:0:0:FFFF:129.144.52.38
+ *
+ * @param String $ip a valid IPv6-address (hex format)
+ * @param Boolean $uncompress if true, the address will be uncompressed
+ * before processing
+ *
+ * @return Array [0] contains the IPv6 part,
+ * [1] the IPv4 part (hex format)
+ * @access public
+ * @static
+ */
+ static function SplitV64($ip, $uncompress = true)
+ {
+ $ip = Net_IPv6::removeNetmaskSpec($ip);
+
+ if ($uncompress) {
+
+ $ip = Net_IPv6::Uncompress($ip);
+
+ }
+
+ if (strstr($ip, '.')) {
+
+ $pos = strrpos($ip, ':');
+ $ip{$pos} = '_';
+ $ipPart = explode('_', $ip);
+
+ return $ipPart;
+
+ } else {
+
+ return array($ip, "");
+
+ }
+ }
+
+ // }}}
+ // {{{ checkIPv6()
+
+ /**
+ * Checks an IPv6 address
+ *
+ * Checks if the given IP is IPv6-compatible
+ *
+ * @param String $ip a valid IPv6-address
+ *
+ * @return Boolean true if $ip is an IPv6 address
+ * @access public
+ * @static
+ */
+ static function checkIPv6($ip)
+ {
+
+ $elements = Net_IPv6::separate($ip);
+
+ $ip = $elements[0];
+
+ if('' != $elements[1] && ( !is_numeric($elements[1]) || 0 > $elements[1] || 128 < $elements[1])) {
+
+ return false;
+
+ }
+
+ $ipPart = Net_IPv6::SplitV64($ip);
+ $count = 0;
+
+ if (!empty($ipPart[0])) {
+ $ipv6 = explode(':', $ipPart[0]);
+
+ foreach($ipv6 as $element) { // made a validate precheck
+ if(!preg_match('/[0-9a-fA-F]*/', $element)) {
+ return false;
+ }
+ }
+
+ for ($i = 0; $i < count($ipv6); $i++) {
+
+ if(4 < strlen($ipv6[$i])) {
+
+ return false;
+
+ }
+
+ $dec = hexdec($ipv6[$i]);
+ $hex = strtoupper(preg_replace("/^[0]{1,3}(.*[0-9a-fA-F])$/",
+ "\\1",
+ $ipv6[$i]));
+
+ if ($ipv6[$i] >= 0 && $dec <= 65535
+ && $hex == strtoupper(dechex($dec))) {
+
+ $count++;
+
+ }
+
+ }
+
+ if (8 == $count) {
+
+ return true;
+
+ } else if (6 == $count and !empty($ipPart[1])) {
+
+ $ipv4 = explode('.', $ipPart[1]);
+ $count = 0;
+
+ for ($i = 0; $i < count($ipv4); $i++) {
+
+ if ($ipv4[$i] >= 0 && (integer)$ipv4[$i] <= 255
+ && preg_match("/^\d{1,3}$/", $ipv4[$i])) {
+
+ $count++;
+
+ }
+
+ }
+
+ if (4 == $count) {
+
+ return true;
+
+ }
+
+ } else {
+
+ return false;
+
+ }
+
+ } else {
+
+ return false;
+
+ }
+
+ }
+
+ // }}}
+
+ // {{{ _parseAddress()
+
+ /**
+ * Returns the lowest and highest IPv6 address
+ * for a given IP and netmask specification
+ *
+ * The netmask may be a part of the $ip or
+ * the number of netmask bits is provided via $bits
+ *
+ * The result is an indexed array. The key 'start'
+ * contains the lowest possible IP address. The key
+ * 'end' the highest address.
+ *
+ * @param String $ipToParse the IPv6 address
+ * @param String $bits the optional count of netmask bits
+ *
+ * @return Array ['start', 'end'] the lowest and highest IPv6 address
+ * @access public
+ * @static
+ * @author Nicholas Williams
+ */
+
+ static function parseAddress($ipToParse, $bits = null)
+ {
+
+ $ip = null;
+ $bitmask = null;
+
+ if ( null == $bits ) {
+
+ $elements = explode('/', $ipToParse);
+
+ if ( 2 == count($elements) ) {
+
+ $ip = Net_IPv6::uncompress($elements[0]);
+ $bitmask = $elements[1];
+
+ } else {
+
+ include_once 'PEAR.inc';
+
+ return PEAR::raiseError(NET_IPV6_NO_NETMASK_MSG,
+ NET_IPV6_NO_NETMASK);
+ }
+ } else {
+
+ $ip = Net_IPv6::uncompress($ipToParse);
+ $bitmask = $bits;
+
+ }
+
+ $binNetmask = str_repeat('1', $bitmask).
+ str_repeat('0', 128 - $bitmask);
+ $maxNetmask = str_repeat('1', 128);
+ $netmask = Net_IPv6::_bin2Ip($binNetmask);
+
+ $startAddress = Net_IPv6::_bin2Ip(Net_IPv6::_ip2Bin($ip)
+ & $binNetmask);
+ $endAddress = Net_IPv6::_bin2Ip(Net_IPv6::_ip2Bin($ip)
+ | ($binNetmask ^ $maxNetmask));
+
+ return array('start' => $startAddress, 'end' => $endAddress);
+ }
+
+ // }}}
+
+ // {{{ _ip2Bin()
+
+ /**
+ * Converts an IPv6 address from Hex into Binary representation.
+ *
+ * @param String $ip the IP to convert (a:b:c:d:e:f:g:h),
+ * compressed IPs are allowed
+ *
+ * @return String the binary representation
+ * @access private
+ @ @since 1.1.0
+ */
+ static function _ip2Bin($ip)
+ {
+ $binstr = '';
+
+ $ip = Net_IPv6::removeNetmaskSpec($ip);
+ $ip = Net_IPv6::Uncompress($ip);
+
+ $parts = explode(':', $ip);
+
+ foreach ( $parts as $v ) {
+
+ $str = base_convert($v, 16, 2);
+ $binstr .= str_pad($str, 16, '0', STR_PAD_LEFT);
+
+ }
+
+ return $binstr;
+ }
+
+ // }}}
+ // {{{ _bin2Ip()
+
+ /**
+ * Converts an IPv6 address from Binary into Hex representation.
+ *
+ * @param String $bin the IP address as binary
+ *
+ * @return String the uncompressed Hex representation
+ * @access private
+ @ @since 1.1.0
+ */
+ static function _bin2Ip($bin)
+ {
+ $ip = "";
+
+ if (strlen($bin) < 128) {
+
+ $bin = str_pad($bin, 128, '0', STR_PAD_LEFT);
+
+ }
+
+ $parts = str_split($bin, "16");
+
+ foreach ( $parts as $v ) {
+
+ $str = base_convert($v, 2, 16);
+ $ip .= $str.":";
+
+ }
+
+ $ip = substr($ip, 0, -1);
+
+ return $ip;
+ }
+
+ // }}}
+}
+// }}}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * c-hanging-comment-ender-p: nil
+ * End:
+ */
+
+?>
diff --git a/src/etc/inc/PEAR.inc b/src/etc/inc/PEAR.inc
new file mode 100644
index 0000000..a280602
--- /dev/null
+++ b/src/etc/inc/PEAR.inc
@@ -0,0 +1,1103 @@
+<?php
+/**
+ * PEAR, the PHP Extension and Application Repository
+ *
+ * PEAR class and PEAR_Error class
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Sterling Hughes <sterling@php.net>
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2010 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+
+/**#@+
+ * ERROR constants
+ */
+define('PEAR_ERROR_RETURN', 1);
+define('PEAR_ERROR_PRINT', 2);
+define('PEAR_ERROR_TRIGGER', 4);
+define('PEAR_ERROR_DIE', 8);
+define('PEAR_ERROR_CALLBACK', 16);
+/**
+ * WARNING: obsolete
+ * @deprecated
+ */
+define('PEAR_ERROR_EXCEPTION', 32);
+/**#@-*/
+define('PEAR_ZE2', (function_exists('version_compare') &&
+ version_compare(zend_version(), "2-dev", "ge")));
+
+if (substr(PHP_OS, 0, 3) == 'WIN') {
+ define('OS_WINDOWS', true);
+ define('OS_UNIX', false);
+ define('PEAR_OS', 'Windows');
+} else {
+ define('OS_WINDOWS', false);
+ define('OS_UNIX', true);
+ define('PEAR_OS', 'Unix'); // blatant assumption
+}
+
+$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN;
+$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE;
+$GLOBALS['_PEAR_destructor_object_list'] = array();
+$GLOBALS['_PEAR_shutdown_funcs'] = array();
+$GLOBALS['_PEAR_error_handler_stack'] = array();
+
+@ini_set('track_errors', true);
+
+/**
+ * Base class for other PEAR classes. Provides rudimentary
+ * emulation of destructors.
+ *
+ * If you want a destructor in your class, inherit PEAR and make a
+ * destructor method called _yourclassname (same name as the
+ * constructor, but with a "_" prefix). Also, in your constructor you
+ * have to call the PEAR constructor: $this->PEAR();.
+ * The destructor method will be called without parameters. Note that
+ * at in some SAPI implementations (such as Apache), any output during
+ * the request shutdown (in which destructors are called) seems to be
+ * discarded. If you need to get any debug information from your
+ * destructor, use error_log(), syslog() or something similar.
+ *
+ * IMPORTANT! To use the emulated destructors you need to create the
+ * objects by reference: $obj =& new PEAR_child;
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V. Cox <cox@idecnet.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2006 The PHP Group
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @see PEAR_Error
+ * @since Class available since PHP 4.0.2
+ * @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear
+ */
+class PEAR
+{
+ /**
+ * Whether to enable internal debug messages.
+ *
+ * @var bool
+ * @access private
+ */
+ var $_debug = false;
+
+ /**
+ * Default error mode for this object.
+ *
+ * @var int
+ * @access private
+ */
+ var $_default_error_mode = null;
+
+ /**
+ * Default error options used for this object when error mode
+ * is PEAR_ERROR_TRIGGER.
+ *
+ * @var int
+ * @access private
+ */
+ var $_default_error_options = null;
+
+ /**
+ * Default error handler (callback) for this object, if error mode is
+ * PEAR_ERROR_CALLBACK.
+ *
+ * @var string
+ * @access private
+ */
+ var $_default_error_handler = '';
+
+ /**
+ * Which class to use for error objects.
+ *
+ * @var string
+ * @access private
+ */
+ var $_error_class = 'PEAR_Error';
+
+ /**
+ * An array of expected errors.
+ *
+ * @var array
+ * @access private
+ */
+ var $_expected_errors = array();
+
+ /**
+ * Constructor. Registers this object in
+ * $_PEAR_destructor_object_list for destructor emulation if a
+ * destructor object exists.
+ *
+ * @param string $error_class (optional) which class to use for
+ * error objects, defaults to PEAR_Error.
+ * @access public
+ * @return void
+ */
+ function PEAR($error_class = null)
+ {
+ $classname = strtolower(get_class($this));
+ if ($this->_debug) {
+ print "PEAR constructor called, class=$classname\n";
+ }
+
+ if ($error_class !== null) {
+ $this->_error_class = $error_class;
+ }
+
+ while ($classname && strcasecmp($classname, "pear")) {
+ $destructor = "_$classname";
+ if (method_exists($this, $destructor)) {
+ global $_PEAR_destructor_object_list;
+ $_PEAR_destructor_object_list[] = &$this;
+ if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
+ register_shutdown_function("_PEAR_call_destructors");
+ $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
+ }
+ break;
+ } else {
+ $classname = get_parent_class($classname);
+ }
+ }
+ }
+
+ /**
+ * Destructor (the emulated type of...). Does nothing right now,
+ * but is included for forward compatibility, so subclass
+ * destructors should always call it.
+ *
+ * See the note in the class description about output from
+ * destructors.
+ *
+ * @access public
+ * @return void
+ */
+ function _PEAR() {
+ if ($this->_debug) {
+ printf("PEAR destructor called, class=%s\n", strtolower(get_class($this)));
+ }
+ }
+
+ /**
+ * If you have a class that's mostly/entirely static, and you need static
+ * properties, you can use this method to simulate them. Eg. in your method(s)
+ * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar');
+ * You MUST use a reference, or they will not persist!
+ *
+ * @access public
+ * @param string $class The calling classname, to prevent clashes
+ * @param string $var The variable to retrieve.
+ * @return mixed A reference to the variable. If not set it will be
+ * auto initialised to NULL.
+ */
+ function &getStaticProperty($class, $var)
+ {
+ static $properties;
+ if (!isset($properties[$class])) {
+ $properties[$class] = array();
+ }
+
+ if (!array_key_exists($var, $properties[$class])) {
+ $properties[$class][$var] = null;
+ }
+
+ return $properties[$class][$var];
+ }
+
+ /**
+ * Use this function to register a shutdown method for static
+ * classes.
+ *
+ * @access public
+ * @param mixed $func The function name (or array of class/method) to call
+ * @param mixed $args The arguments to pass to the function
+ * @return void
+ */
+ function registerShutdownFunc($func, $args = array())
+ {
+ // if we are called statically, there is a potential
+ // that no shutdown func is registered. Bug #6445
+ if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
+ register_shutdown_function("_PEAR_call_destructors");
+ $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
+ }
+ $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args);
+ }
+
+ /**
+ * Tell whether a value is a PEAR error.
+ *
+ * @param mixed $data the value to test
+ * @param int $code if $data is an error object, return true
+ * only if $code is a string and
+ * $obj->getMessage() == $code or
+ * $code is an integer and $obj->getCode() == $code
+ * @access public
+ * @return bool true if parameter is an error
+ */
+ function isError($data, $code = null)
+ {
+ if (!is_object($data)) {
+ return false;
+ }
+ if (!is_a($data, 'PEAR_Error')) {
+ return false;
+ }
+
+ if (is_null($code)) {
+ return true;
+ } elseif (is_string($code)) {
+ return $data->getMessage() == $code;
+ }
+
+ return $data->getCode() == $code;
+ }
+
+ /**
+ * Sets how errors generated by this object should be handled.
+ * Can be invoked both in objects and statically. If called
+ * statically, setErrorHandling sets the default behaviour for all
+ * PEAR objects. If called in an object, setErrorHandling sets
+ * the default behaviour for that object.
+ *
+ * @param int $mode
+ * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
+ * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
+ * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION.
+ *
+ * @param mixed $options
+ * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one
+ * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
+ *
+ * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected
+ * to be the callback function or method. A callback
+ * function is a string with the name of the function, a
+ * callback method is an array of two elements: the element
+ * at index 0 is the object, and the element at index 1 is
+ * the name of the method to call in the object.
+ *
+ * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is
+ * a printf format string used when printing the error
+ * message.
+ *
+ * @access public
+ * @return void
+ * @see PEAR_ERROR_RETURN
+ * @see PEAR_ERROR_PRINT
+ * @see PEAR_ERROR_TRIGGER
+ * @see PEAR_ERROR_DIE
+ * @see PEAR_ERROR_CALLBACK
+ * @see PEAR_ERROR_EXCEPTION
+ *
+ * @since PHP 4.0.5
+ */
+ function setErrorHandling($mode = null, $options = null)
+ {
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $setmode = &$this->_default_error_mode;
+ $setoptions = &$this->_default_error_options;
+ } else {
+ $setmode = &$GLOBALS['_PEAR_default_error_mode'];
+ $setoptions = &$GLOBALS['_PEAR_default_error_options'];
+ }
+
+ switch ($mode) {
+ case PEAR_ERROR_EXCEPTION:
+ case PEAR_ERROR_RETURN:
+ case PEAR_ERROR_PRINT:
+ case PEAR_ERROR_TRIGGER:
+ case PEAR_ERROR_DIE:
+ case null:
+ $setmode = $mode;
+ $setoptions = $options;
+ break;
+
+ case PEAR_ERROR_CALLBACK:
+ $setmode = $mode;
+ // class/object method callback
+ if (is_callable($options)) {
+ $setoptions = $options;
+ } else {
+ trigger_error("invalid error callback", E_USER_WARNING);
+ }
+ break;
+
+ default:
+ trigger_error("invalid error mode", E_USER_WARNING);
+ break;
+ }
+ }
+
+ /**
+ * This method is used to tell which errors you expect to get.
+ * Expected errors are always returned with error mode
+ * PEAR_ERROR_RETURN. Expected error codes are stored in a stack,
+ * and this method pushes a new element onto it. The list of
+ * expected errors are in effect until they are popped off the
+ * stack with the popExpect() method.
+ *
+ * Note that this method can not be called statically
+ *
+ * @param mixed $code a single error code or an array of error codes to expect
+ *
+ * @return int the new depth of the "expected errors" stack
+ * @access public
+ */
+ function expectError($code = '*')
+ {
+ if (is_array($code)) {
+ array_push($this->_expected_errors, $code);
+ } else {
+ array_push($this->_expected_errors, array($code));
+ }
+ return count($this->_expected_errors);
+ }
+
+ /**
+ * This method pops one element off the expected error codes
+ * stack.
+ *
+ * @return array the list of error codes that were popped
+ */
+ function popExpect()
+ {
+ return array_pop($this->_expected_errors);
+ }
+
+ /**
+ * This method checks unsets an error code if available
+ *
+ * @param mixed error code
+ * @return bool true if the error code was unset, false otherwise
+ * @access private
+ * @since PHP 4.3.0
+ */
+ function _checkDelExpect($error_code)
+ {
+ $deleted = false;
+ foreach ($this->_expected_errors as $key => $error_array) {
+ if (in_array($error_code, $error_array)) {
+ unset($this->_expected_errors[$key][array_search($error_code, $error_array)]);
+ $deleted = true;
+ }
+
+ // clean up empty arrays
+ if (0 == count($this->_expected_errors[$key])) {
+ unset($this->_expected_errors[$key]);
+ }
+ }
+
+ return $deleted;
+ }
+
+ /**
+ * This method deletes all occurrences of the specified element from
+ * the expected error codes stack.
+ *
+ * @param mixed $error_code error code that should be deleted
+ * @return mixed list of error codes that were deleted or error
+ * @access public
+ * @since PHP 4.3.0
+ */
+ function delExpect($error_code)
+ {
+ $deleted = false;
+ if ((is_array($error_code) && (0 != count($error_code)))) {
+ // $error_code is a non-empty array here; we walk through it trying
+ // to unset all values
+ foreach ($error_code as $key => $error) {
+ $deleted = $this->_checkDelExpect($error) ? true : false;
+ }
+
+ return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
+ } elseif (!empty($error_code)) {
+ // $error_code comes alone, trying to unset it
+ if ($this->_checkDelExpect($error_code)) {
+ return true;
+ }
+
+ return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
+ }
+
+ // $error_code is empty
+ return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME
+ }
+
+ /**
+ * This method is a wrapper that returns an instance of the
+ * configured error class with this object's default error
+ * handling applied. If the $mode and $options parameters are not
+ * specified, the object's defaults are used.
+ *
+ * @param mixed $message a text error message or a PEAR error object
+ *
+ * @param int $code a numeric error code (it is up to your class
+ * to define these if you want to use codes)
+ *
+ * @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
+ * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
+ * PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION.
+ *
+ * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter
+ * specifies the PHP-internal error level (one of
+ * E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
+ * If $mode is PEAR_ERROR_CALLBACK, this
+ * parameter specifies the callback function or
+ * method. In other error modes this parameter
+ * is ignored.
+ *
+ * @param string $userinfo If you need to pass along for example debug
+ * information, this parameter is meant for that.
+ *
+ * @param string $error_class The returned error object will be
+ * instantiated from this class, if specified.
+ *
+ * @param bool $skipmsg If true, raiseError will only pass error codes,
+ * the error message parameter will be dropped.
+ *
+ * @access public
+ * @return object a PEAR error object
+ * @see PEAR::setErrorHandling
+ * @since PHP 4.0.5
+ */
+ function &raiseError($message = null,
+ $code = null,
+ $mode = null,
+ $options = null,
+ $userinfo = null,
+ $error_class = null,
+ $skipmsg = false)
+ {
+ // The error is yet a PEAR error object
+ if (is_object($message)) {
+ $code = $message->getCode();
+ $userinfo = $message->getUserInfo();
+ $error_class = $message->getType();
+ $message->error_message_prefix = '';
+ $message = $message->getMessage();
+
+ // Make sure right data gets passed.
+ $r = new ReflectionClass($error_class);
+ $c = $r->getConstructor();
+ $p = array_shift($c->getParameters());
+ $skipmsg = ($p->getName() != 'message');
+ }
+
+ if (
+ isset($this) &&
+ isset($this->_expected_errors) &&
+ count($this->_expected_errors) > 0 &&
+ count($exp = end($this->_expected_errors))
+ ) {
+ if ($exp[0] == "*" ||
+ (is_int(reset($exp)) && in_array($code, $exp)) ||
+ (is_string(reset($exp)) && in_array($message, $exp))
+ ) {
+ $mode = PEAR_ERROR_RETURN;
+ }
+ }
+
+ // No mode given, try global ones
+ if ($mode === null) {
+ // Class error handler
+ if (isset($this) && isset($this->_default_error_mode)) {
+ $mode = $this->_default_error_mode;
+ $options = $this->_default_error_options;
+ // Global error handler
+ } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) {
+ $mode = $GLOBALS['_PEAR_default_error_mode'];
+ $options = $GLOBALS['_PEAR_default_error_options'];
+ }
+ }
+
+ if ($error_class !== null) {
+ $ec = $error_class;
+ } elseif (isset($this) && isset($this->_error_class)) {
+ $ec = $this->_error_class;
+ } else {
+ $ec = 'PEAR_Error';
+ }
+
+ if (intval(PHP_VERSION) < 5) {
+ // little non-eval hack to fix bug #12147
+ include 'PEAR/FixPHP5PEARWarnings.php';
+ return $a;
+ }
+
+ if ($skipmsg) {
+ $a = new $ec($code, $mode, $options, $userinfo);
+ } else {
+ $a = new $ec($message, $code, $mode, $options, $userinfo);
+ }
+
+ return $a;
+ }
+
+ /**
+ * Simpler form of raiseError with fewer options. In most cases
+ * message, code and userinfo are enough.
+ *
+ * @param mixed $message a text error message or a PEAR error object
+ *
+ * @param int $code a numeric error code (it is up to your class
+ * to define these if you want to use codes)
+ *
+ * @param string $userinfo If you need to pass along for example debug
+ * information, this parameter is meant for that.
+ *
+ * @access public
+ * @return object a PEAR error object
+ * @see PEAR::raiseError
+ */
+ function &throwError($message = null, $code = null, $userinfo = null)
+ {
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $a = &$this->raiseError($message, $code, null, null, $userinfo);
+ return $a;
+ }
+
+ $a = &PEAR::raiseError($message, $code, null, null, $userinfo);
+ return $a;
+ }
+
+ function staticPushErrorHandling($mode, $options = null)
+ {
+ $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+ $def_mode = &$GLOBALS['_PEAR_default_error_mode'];
+ $def_options = &$GLOBALS['_PEAR_default_error_options'];
+ $stack[] = array($def_mode, $def_options);
+ switch ($mode) {
+ case PEAR_ERROR_EXCEPTION:
+ case PEAR_ERROR_RETURN:
+ case PEAR_ERROR_PRINT:
+ case PEAR_ERROR_TRIGGER:
+ case PEAR_ERROR_DIE:
+ case null:
+ $def_mode = $mode;
+ $def_options = $options;
+ break;
+
+ case PEAR_ERROR_CALLBACK:
+ $def_mode = $mode;
+ // class/object method callback
+ if (is_callable($options)) {
+ $def_options = $options;
+ } else {
+ trigger_error("invalid error callback", E_USER_WARNING);
+ }
+ break;
+
+ default:
+ trigger_error("invalid error mode", E_USER_WARNING);
+ break;
+ }
+ $stack[] = array($mode, $options);
+ return true;
+ }
+
+ function staticPopErrorHandling()
+ {
+ $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+ $setmode = &$GLOBALS['_PEAR_default_error_mode'];
+ $setoptions = &$GLOBALS['_PEAR_default_error_options'];
+ array_pop($stack);
+ list($mode, $options) = $stack[sizeof($stack) - 1];
+ array_pop($stack);
+ switch ($mode) {
+ case PEAR_ERROR_EXCEPTION:
+ case PEAR_ERROR_RETURN:
+ case PEAR_ERROR_PRINT:
+ case PEAR_ERROR_TRIGGER:
+ case PEAR_ERROR_DIE:
+ case null:
+ $setmode = $mode;
+ $setoptions = $options;
+ break;
+
+ case PEAR_ERROR_CALLBACK:
+ $setmode = $mode;
+ // class/object method callback
+ if (is_callable($options)) {
+ $setoptions = $options;
+ } else {
+ trigger_error("invalid error callback", E_USER_WARNING);
+ }
+ break;
+
+ default:
+ trigger_error("invalid error mode", E_USER_WARNING);
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * Push a new error handler on top of the error handler options stack. With this
+ * you can easily override the actual error handler for some code and restore
+ * it later with popErrorHandling.
+ *
+ * @param mixed $mode (same as setErrorHandling)
+ * @param mixed $options (same as setErrorHandling)
+ *
+ * @return bool Always true
+ *
+ * @see PEAR::setErrorHandling
+ */
+ function pushErrorHandling($mode, $options = null)
+ {
+ $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $def_mode = &$this->_default_error_mode;
+ $def_options = &$this->_default_error_options;
+ } else {
+ $def_mode = &$GLOBALS['_PEAR_default_error_mode'];
+ $def_options = &$GLOBALS['_PEAR_default_error_options'];
+ }
+ $stack[] = array($def_mode, $def_options);
+
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $this->setErrorHandling($mode, $options);
+ } else {
+ PEAR::setErrorHandling($mode, $options);
+ }
+ $stack[] = array($mode, $options);
+ return true;
+ }
+
+ /**
+ * Pop the last error handler used
+ *
+ * @return bool Always true
+ *
+ * @see PEAR::pushErrorHandling
+ */
+ function popErrorHandling()
+ {
+ $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+ array_pop($stack);
+ list($mode, $options) = $stack[sizeof($stack) - 1];
+ array_pop($stack);
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $this->setErrorHandling($mode, $options);
+ } else {
+ PEAR::setErrorHandling($mode, $options);
+ }
+ return true;
+ }
+
+ /**
+ * OS independent PHP extension load. Remember to take care
+ * on the correct extension name for case sensitive OSes.
+ *
+ * @param string $ext The extension name
+ * @return bool Success or not on the dl() call
+ */
+ static function loadExtension($ext)
+ {
+ if (extension_loaded($ext)) {
+ return true;
+ }
+
+ // if either returns true dl() will produce a FATAL error, stop that
+ if (
+ function_exists('dl') === false ||
+ ini_get('enable_dl') != 1 ||
+ ini_get('safe_mode') == 1
+ ) {
+ return false;
+ }
+
+ if (OS_WINDOWS) {
+ $suffix = '.dll';
+ } elseif (PHP_OS == 'HP-UX') {
+ $suffix = '.sl';
+ } elseif (PHP_OS == 'AIX') {
+ $suffix = '.a';
+ } elseif (PHP_OS == 'OSX') {
+ $suffix = '.bundle';
+ } else {
+ $suffix = '.so';
+ }
+
+ return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
+ }
+}
+
+if (PEAR_ZE2) {
+ /**
+ * This is only meant for PHP 5 to get rid of certain strict warning
+ * that doesn't get hidden since it's in the shutdown function
+ */
+ class PEAR5
+ {
+ /**
+ * If you have a class that's mostly/entirely static, and you need static
+ * properties, you can use this method to simulate them. Eg. in your method(s)
+ * do this: $myVar = &PEAR5::getStaticProperty('myclass', 'myVar');
+ * You MUST use a reference, or they will not persist!
+ *
+ * @access public
+ * @param string $class The calling classname, to prevent clashes
+ * @param string $var The variable to retrieve.
+ * @return mixed A reference to the variable. If not set it will be
+ * auto initialised to NULL.
+ */
+ static function &getStaticProperty($class, $var)
+ {
+ static $properties;
+ if (!isset($properties[$class])) {
+ $properties[$class] = array();
+ }
+
+ if (!array_key_exists($var, $properties[$class])) {
+ $properties[$class][$var] = null;
+ }
+
+ return $properties[$class][$var];
+ }
+ }
+}
+
+function _PEAR_call_destructors()
+{
+ global $_PEAR_destructor_object_list;
+ if (is_array($_PEAR_destructor_object_list) &&
+ sizeof($_PEAR_destructor_object_list))
+ {
+ reset($_PEAR_destructor_object_list);
+ if (PEAR_ZE2) {
+ $destructLifoExists = PEAR5::getStaticProperty('PEAR', 'destructlifo');
+ } else {
+ $destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo');
+ }
+
+ if ($destructLifoExists) {
+ $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list);
+ }
+
+ while (list($k, $objref) = each($_PEAR_destructor_object_list)) {
+ $classname = get_class($objref);
+ while ($classname) {
+ $destructor = "_$classname";
+ if (method_exists($objref, $destructor)) {
+ $objref->$destructor();
+ break;
+ } else {
+ $classname = get_parent_class($classname);
+ }
+ }
+ }
+ // Empty the object list to ensure that destructors are
+ // not called more than once.
+ $_PEAR_destructor_object_list = array();
+ }
+
+ // Now call the shutdown functions
+ if (
+ isset($GLOBALS['_PEAR_shutdown_funcs']) &&
+ is_array($GLOBALS['_PEAR_shutdown_funcs']) &&
+ !empty($GLOBALS['_PEAR_shutdown_funcs'])
+ ) {
+ foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) {
+ call_user_func_array($value[0], $value[1]);
+ }
+ }
+}
+
+/**
+ * Standard PEAR error class for PHP 4
+ *
+ * This class is superseded by {@link PEAR_Exception} in PHP 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V. Cox <cox@idecnet.com>
+ * @author Gregory Beaver <cellog@php.net>
+ * @copyright 1997-2006 The PHP Group
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/manual/en/core.pear.pear-error.php
+ * @see PEAR::raiseError(), PEAR::throwError()
+ * @since Class available since PHP 4.0.2
+ */
+class PEAR_Error
+{
+ var $error_message_prefix = '';
+ var $mode = PEAR_ERROR_RETURN;
+ var $level = E_USER_NOTICE;
+ var $code = -1;
+ var $message = '';
+ var $userinfo = '';
+ var $backtrace = null;
+
+ /**
+ * PEAR_Error constructor
+ *
+ * @param string $message message
+ *
+ * @param int $code (optional) error code
+ *
+ * @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN,
+ * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER,
+ * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION
+ *
+ * @param mixed $options (optional) error level, _OR_ in the case of
+ * PEAR_ERROR_CALLBACK, the callback function or object/method
+ * tuple.
+ *
+ * @param string $userinfo (optional) additional user/debug info
+ *
+ * @access public
+ *
+ */
+ function PEAR_Error($message = 'unknown error', $code = null,
+ $mode = null, $options = null, $userinfo = null)
+ {
+ if ($mode === null) {
+ $mode = PEAR_ERROR_RETURN;
+ }
+ $this->message = $message;
+ $this->code = $code;
+ $this->mode = $mode;
+ $this->userinfo = $userinfo;
+
+ if (PEAR_ZE2) {
+ $skiptrace = PEAR5::getStaticProperty('PEAR_Error', 'skiptrace');
+ } else {
+ $skiptrace = PEAR::getStaticProperty('PEAR_Error', 'skiptrace');
+ }
+
+ if (!$skiptrace) {
+ $this->backtrace = debug_backtrace();
+ if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) {
+ unset($this->backtrace[0]['object']);
+ }
+ }
+
+ if ($mode & PEAR_ERROR_CALLBACK) {
+ $this->level = E_USER_NOTICE;
+ $this->callback = $options;
+ } else {
+ if ($options === null) {
+ $options = E_USER_NOTICE;
+ }
+
+ $this->level = $options;
+ $this->callback = null;
+ }
+
+ if ($this->mode & PEAR_ERROR_PRINT) {
+ if (is_null($options) || is_int($options)) {
+ $format = "%s";
+ } else {
+ $format = $options;
+ }
+
+ printf($format, $this->getMessage());
+ }
+
+ if ($this->mode & PEAR_ERROR_TRIGGER) {
+ trigger_error($this->getMessage(), $this->level);
+ }
+
+ if ($this->mode & PEAR_ERROR_DIE) {
+ $msg = $this->getMessage();
+ if (is_null($options) || is_int($options)) {
+ $format = "%s";
+ if (substr($msg, -1) != "\n") {
+ $msg .= "\n";
+ }
+ } else {
+ $format = $options;
+ }
+ die(sprintf($format, $msg));
+ }
+
+ if ($this->mode & PEAR_ERROR_CALLBACK && is_callable($this->callback)) {
+ call_user_func($this->callback, $this);
+ }
+
+ if ($this->mode & PEAR_ERROR_EXCEPTION) {
+ trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING);
+ eval('$e = new Exception($this->message, $this->code);throw($e);');
+ }
+ }
+
+ /**
+ * Get the error mode from an error object.
+ *
+ * @return int error mode
+ * @access public
+ */
+ function getMode()
+ {
+ return $this->mode;
+ }
+
+ /**
+ * Get the callback function/method from an error object.
+ *
+ * @return mixed callback function or object/method array
+ * @access public
+ */
+ function getCallback()
+ {
+ return $this->callback;
+ }
+
+ /**
+ * Get the error message from an error object.
+ *
+ * @return string full error message
+ * @access public
+ */
+ function getMessage()
+ {
+ return ($this->error_message_prefix . $this->message);
+ }
+
+ /**
+ * Get error code from an error object
+ *
+ * @return int error code
+ * @access public
+ */
+ function getCode()
+ {
+ return $this->code;
+ }
+
+ /**
+ * Get the name of this error/exception.
+ *
+ * @return string error/exception name (type)
+ * @access public
+ */
+ function getType()
+ {
+ return get_class($this);
+ }
+
+ /**
+ * Get additional user-supplied information.
+ *
+ * @return string user-supplied information
+ * @access public
+ */
+ function getUserInfo()
+ {
+ return $this->userinfo;
+ }
+
+ /**
+ * Get additional debug information supplied by the application.
+ *
+ * @return string debug information
+ * @access public
+ */
+ function getDebugInfo()
+ {
+ return $this->getUserInfo();
+ }
+
+ /**
+ * Get the call backtrace from where the error was generated.
+ * Supported with PHP 4.3.0 or newer.
+ *
+ * @param int $frame (optional) what frame to fetch
+ * @return array Backtrace, or NULL if not available.
+ * @access public
+ */
+ function getBacktrace($frame = null)
+ {
+ if (defined('PEAR_IGNORE_BACKTRACE')) {
+ return null;
+ }
+ if ($frame === null) {
+ return $this->backtrace;
+ }
+ return $this->backtrace[$frame];
+ }
+
+ function addUserInfo($info)
+ {
+ if (empty($this->userinfo)) {
+ $this->userinfo = $info;
+ } else {
+ $this->userinfo .= " ** $info";
+ }
+ }
+
+ function __toString()
+ {
+ return $this->getMessage();
+ }
+
+ /**
+ * Make a string representation of this object.
+ *
+ * @return string a string with an object summary
+ * @access public
+ */
+ function toString()
+ {
+ $modes = array();
+ $levels = array(E_USER_NOTICE => 'notice',
+ E_USER_WARNING => 'warning',
+ E_USER_ERROR => 'error');
+ if ($this->mode & PEAR_ERROR_CALLBACK) {
+ if (is_array($this->callback)) {
+ $callback = (is_object($this->callback[0]) ?
+ strtolower(get_class($this->callback[0])) :
+ $this->callback[0]) . '::' .
+ $this->callback[1];
+ } else {
+ $callback = $this->callback;
+ }
+ return sprintf('[%s: message="%s" code=%d mode=callback '.
+ 'callback=%s prefix="%s" info="%s"]',
+ strtolower(get_class($this)), $this->message, $this->code,
+ $callback, $this->error_message_prefix,
+ $this->userinfo);
+ }
+ if ($this->mode & PEAR_ERROR_PRINT) {
+ $modes[] = 'print';
+ }
+ if ($this->mode & PEAR_ERROR_TRIGGER) {
+ $modes[] = 'trigger';
+ }
+ if ($this->mode & PEAR_ERROR_DIE) {
+ $modes[] = 'die';
+ }
+ if ($this->mode & PEAR_ERROR_RETURN) {
+ $modes[] = 'return';
+ }
+ return sprintf('[%s: message="%s" code=%d mode=%s level=%s '.
+ 'prefix="%s" info="%s"]',
+ strtolower(get_class($this)), $this->message, $this->code,
+ implode("|", $modes), $levels[$this->level],
+ $this->error_message_prefix,
+ $this->userinfo);
+ }
+}
+
+/*
+ * Local Variables:
+ * mode: php
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/src/etc/inc/auth.inc b/src/etc/inc/auth.inc
new file mode 100644
index 0000000..3c0acaa
--- /dev/null
+++ b/src/etc/inc/auth.inc
@@ -0,0 +1,1627 @@
+<?php
+/* $Id$ */
+/*
+ Copyright (C) 2010 Ermal Luçi
+ All rights reserved.
+
+ Copyright (C) 2007, 2008 Scott Ullrich <sullrich@gmail.com>
+ All rights reserved.
+
+ Copyright (C) 2005-2006 Bill Marquette <bill.marquette@gmail.com>
+ All rights reserved.
+
+ Copyright (C) 2006 Paul Taylor <paultaylor@winn-dixie.com>.
+ All rights reserved.
+
+ Copyright (C) 2003-2006 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.
+
+ pfSense_BUILDER_BINARIES: /usr/sbin/pw /bin/cp
+ pfSense_MODULE: auth
+*/
+
+/*
+ * NOTE : Portions of the mschapv2 support was based on the BSD licensed CHAP.php
+ * file courtesy of Michael Retterklieber.
+ */
+if (!$do_not_include_config_gui_inc) {
+ require_once("config.gui.inc");
+}
+
+// Will be changed to false if security checks fail
+$security_passed = true;
+
+/* If this function doesn't exist, we're being called from Captive Portal or
+ another internal subsystem which does not include authgui.inc */
+if (function_exists("display_error_form") && !isset($config['system']['webgui']['nodnsrebindcheck'])) {
+ /* DNS ReBinding attack prevention. https://redmine.pfsense.org/issues/708 */
+ $found_host = false;
+
+ /* Either a IPv6 address with or without a alternate port */
+ if (strstr($_SERVER['HTTP_HOST'], "]")) {
+ $http_host_port = explode("]", $_SERVER['HTTP_HOST']);
+ /* v6 address has more parts, drop the last part */
+ if (count($http_host_port) > 1) {
+ array_pop($http_host_port);
+ $http_host = str_replace(array("[", "]"), "", implode(":", $http_host_port));
+ } else {
+ $http_host = str_replace(array("[", "]"), "", implode(":", $http_host_port));
+ }
+ } else {
+ $http_host = explode(":", $_SERVER['HTTP_HOST']);
+ $http_host = $http_host[0];
+ }
+ if (is_ipaddr($http_host) or $_SERVER['SERVER_ADDR'] == "127.0.0.1" or
+ strcasecmp($http_host, "localhost") == 0 or $_SERVER['SERVER_ADDR'] == "::1") {
+ $found_host = true;
+ }
+ if (strcasecmp($http_host, $config['system']['hostname'] . "." . $config['system']['domain']) == 0 or
+ strcasecmp($http_host, $config['system']['hostname']) == 0) {
+ $found_host = true;
+ }
+
+ if (is_array($config['dyndnses']['dyndns']) && !$found_host) {
+ foreach ($config['dyndnses']['dyndns'] as $dyndns) {
+ if (strcasecmp($dyndns['host'], $http_host) == 0) {
+ $found_host = true;
+ break;
+ }
+ }
+ }
+
+ if (is_array($config['dnsupdates']['dnsupdate']) && !$found_host) {
+ foreach ($config['dnsupdates']['dnsupdate'] as $rfc2136) {
+ if (strcasecmp($rfc2136['host'], $http_host) == 0) {
+ $found_host = true;
+ break;
+ }
+ }
+ }
+
+ if (!empty($config['system']['webgui']['althostnames']) && !$found_host) {
+ $althosts = explode(" ", $config['system']['webgui']['althostnames']);
+ foreach ($althosts as $ah) {
+ if (strcasecmp($ah, $http_host) == 0 or strcasecmp($ah, $_SERVER['SERVER_ADDR']) == 0) {
+ $found_host = true;
+ break;
+ }
+ }
+ }
+
+ if ($found_host == false) {
+ if (!security_checks_disabled()) {
+ display_error_form("501", gettext("Potential DNS Rebind attack detected, see http://en.wikipedia.org/wiki/DNS_rebinding<br />Try accessing the router by IP address instead of by hostname."));
+ exit;
+ }
+ $security_passed = false;
+ }
+}
+
+// If the HTTP_REFERER is something other than ourselves then disallow.
+if (function_exists("display_error_form") && !isset($config['system']['webgui']['nohttpreferercheck'])) {
+ if ($_SERVER['HTTP_REFERER']) {
+ if (file_exists("{$g['tmp_path']}/setupwizard_lastreferrer")) {
+ if ($_SERVER['HTTP_REFERER'] == file_get_contents("{$g['tmp_path']}/setupwizard_lastreferrer")) {
+ unlink("{$g['tmp_path']}/setupwizard_lastreferrer");
+ header("Refresh: 1; url=index.php");
+ echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";
+ echo "<html><head><title>" . gettext("Redirecting...") . "</title></head><body>" . gettext("Redirecting to the dashboard...") . "</body></html>";
+ exit;
+ }
+ }
+ $found_host = false;
+ $referrer_host = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST);
+ $referrer_host = str_replace(array("[", "]"), "", $referrer_host);
+ if ($referrer_host) {
+ if (strcasecmp($referrer_host, $config['system']['hostname'] . "." . $config['system']['domain']) == 0 ||
+ strcasecmp($referrer_host, $config['system']['hostname']) == 0) {
+ $found_host = true;
+ }
+
+ if (!empty($config['system']['webgui']['althostnames']) && !$found_host) {
+ $althosts = explode(" ", $config['system']['webgui']['althostnames']);
+ foreach ($althosts as $ah) {
+ if (strcasecmp($referrer_host, $ah) == 0) {
+ $found_host = true;
+ break;
+ }
+ }
+ }
+
+ if (is_array($config['dyndnses']['dyndns']) && !$found_host) {
+ foreach ($config['dyndnses']['dyndns'] as $dyndns) {
+ if (strcasecmp($dyndns['host'], $referrer_host) == 0) {
+ $found_host = true;
+ break;
+ }
+ }
+ }
+
+ if (is_array($config['dnsupdates']['dnsupdate']) && !$found_host) {
+ foreach ($config['dnsupdates']['dnsupdate'] as $rfc2136) {
+ if (strcasecmp($rfc2136['host'], $referrer_host) == 0) {
+ $found_host = true;
+ break;
+ }
+ }
+ }
+
+ if (!$found_host) {
+ $interface_list_ips = get_configured_ip_addresses();
+ foreach ($interface_list_ips as $ilips) {
+ if (strcasecmp($referrer_host, $ilips) == 0) {
+ $found_host = true;
+ break;
+ }
+ }
+ $interface_list_ipv6s = get_configured_ipv6_addresses();
+ foreach ($interface_list_ipv6s as $ilipv6s) {
+ if (strcasecmp($referrer_host, $ilipv6s) == 0) {
+ $found_host = true;
+ break;
+ }
+ }
+ if ($referrer_host == "127.0.0.1" || $referrer_host == "localhost") {
+ // allow SSH port forwarded connections and links from localhost
+ $found_host = true;
+ }
+ }
+ }
+ if ($found_host == false) {
+ if (!security_checks_disabled()) {
+ display_error_form("501", "An HTTP_REFERER was detected other than what is defined in System -> Advanced (" . htmlspecialchars($_SERVER['HTTP_REFERER']) . "). You can disable this check if needed in System -> Advanced -> Admin.");
+ exit;
+ }
+ $security_passed = false;
+ }
+ } else {
+ $security_passed = false;
+ }
+}
+
+if (function_exists("display_error_form") && $security_passed) {
+ /* Security checks passed, so it should be OK to turn them back on */
+ restore_security_checks();
+}
+unset($security_passed);
+
+$groupindex = index_groups();
+$userindex = index_users();
+
+function index_groups() {
+ global $g, $debug, $config, $groupindex;
+
+ $groupindex = array();
+
+ if (is_array($config['system']['group'])) {
+ $i = 0;
+ foreach ($config['system']['group'] as $groupent) {
+ $groupindex[$groupent['name']] = $i;
+ $i++;
+ }
+ }
+
+ return ($groupindex);
+}
+
+function index_users() {
+ global $g, $debug, $config;
+
+ if (is_array($config['system']['user'])) {
+ $i = 0;
+ foreach ($config['system']['user'] as $userent) {
+ $userindex[$userent['name']] = $i;
+ $i++;
+ }
+ }
+
+ return ($userindex);
+}
+
+function & getUserEntry($name) {
+ global $debug, $config, $userindex;
+ if (isset($userindex[$name])) {
+ return $config['system']['user'][$userindex[$name]];
+ }
+}
+
+function & getUserEntryByUID($uid) {
+ global $debug, $config;
+
+ if (is_array($config['system']['user'])) {
+ foreach ($config['system']['user'] as & $user) {
+ if ($user['uid'] == $uid) {
+ return $user;
+ }
+ }
+ }
+
+ return false;
+}
+
+function & getGroupEntry($name) {
+ global $debug, $config, $groupindex;
+ if (isset($groupindex[$name])) {
+ return $config['system']['group'][$groupindex[$name]];
+ }
+}
+
+function & getGroupEntryByGID($gid) {
+ global $debug, $config;
+
+ if (is_array($config['system']['group'])) {
+ foreach ($config['system']['group'] as & $group) {
+ if ($group['gid'] == $gid) {
+ return $group;
+ }
+ }
+ }
+
+ return false;
+}
+
+function get_user_privileges(& $user) {
+
+ $privs = $user['priv'];
+ if (!is_array($privs)) {
+ $privs = array();
+ }
+
+ $names = local_user_get_groups($user, true);
+
+ foreach ($names as $name) {
+ $group = getGroupEntry($name);
+ if (is_array($group['priv'])) {
+ $privs = array_merge($privs, $group['priv']);
+ }
+ }
+
+ return $privs;
+}
+
+function userHasPrivilege($userent, $privid = false) {
+
+ if (!$privid || !is_array($userent)) {
+ return false;
+ }
+
+ $privs = get_user_privileges($userent);
+
+ if (!is_array($privs)) {
+ return false;
+ }
+
+ if (!in_array($privid, $privs)) {
+ return false;
+ }
+
+ return true;
+}
+
+function local_backed($username, $passwd) {
+
+ $user = getUserEntry($username);
+ if (!$user) {
+ return false;
+ }
+
+ if (is_account_disabled($username) || is_account_expired($username)) {
+ return false;
+ }
+
+ if ($user['password']) {
+ if (crypt($passwd, $user['password']) == $user['password']) {
+ return true;
+ }
+ }
+
+ if ($user['md5-hash']) {
+ if (md5($passwd) == $user['md5-hash']) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function local_sync_accounts() {
+ global $debug, $config;
+ conf_mount_rw();
+
+ /* remove local users to avoid uid conflicts */
+ $fd = popen("/usr/sbin/pw usershow -a", "r");
+ if ($fd) {
+ while (!feof($fd)) {
+ $line = explode(":", fgets($fd));
+ if (((!strncmp($line[0], "_", 1)) || ($line[2] < 2000) || ($line[2] > 65000)) && ($line[0] != "admin")) {
+ continue;
+ }
+ /*
+ * If a crontab was created to user, pw userdel will be interactive and
+ * can cause issues. Just remove crontab before run it when necessary
+ */
+ unlink_if_exists("/var/cron/tabs/{$line[0]}");
+ $cmd = "/usr/sbin/pw userdel -n '{$line[0]}'";
+ if ($debug) {
+ log_error(sprintf(gettext("Running: %s"), $cmd));
+ }
+ mwexec($cmd);
+ }
+ pclose($fd);
+ }
+
+ /* remove local groups to avoid gid conflicts */
+ $gids = array();
+ $fd = popen("/usr/sbin/pw groupshow -a", "r");
+ if ($fd) {
+ while (!feof($fd)) {
+ $line = explode(":", fgets($fd));
+ if (!strncmp($line[0], "_", 1)) {
+ continue;
+ }
+ if ($line[2] < 2000) {
+ continue;
+ }
+ if ($line[2] > 65000) {
+ continue;
+ }
+ $cmd = "/usr/sbin/pw groupdel {$line[2]}";
+ if ($debug) {
+ log_error(sprintf(gettext("Running: %s"), $cmd));
+ }
+ mwexec($cmd);
+ }
+ pclose($fd);
+ }
+
+ /* make sure the all group exists */
+ $allgrp = getGroupEntryByGID(1998);
+ local_group_set($allgrp, true);
+
+ /* sync all local users */
+ if (is_array($config['system']['user'])) {
+ foreach ($config['system']['user'] as $user) {
+ local_user_set($user);
+ }
+ }
+
+ /* sync all local groups */
+ if (is_array($config['system']['group'])) {
+ foreach ($config['system']['group'] as $group) {
+ local_group_set($group);
+ }
+ }
+
+ conf_mount_ro();
+
+}
+
+function local_user_set(& $user) {
+ global $g, $debug;
+
+ if (empty($user['password'])) {
+ log_error("There is something wrong in your config because user {$user['name']} password is missing!");
+ return;
+ }
+
+ conf_mount_rw();
+
+ $home_base = "/home/";
+ $user_uid = $user['uid'];
+ $user_name = $user['name'];
+ $user_home = "{$home_base}{$user_name}";
+ $user_shell = "/etc/rc.initial";
+ $user_group = "nobody";
+
+ // Ensure $home_base exists and is writable
+ if (!is_dir($home_base)) {
+ mkdir($home_base, 0755);
+ }
+
+ $lock_account = false;
+ /* configure shell type */
+ /* Cases here should be ordered by most privileged to least privileged. */
+ if (userHasPrivilege($user, "user-shell-access") || userHasPrivilege($user, "page-all")) {
+ $user_shell = "/bin/tcsh";
+ } elseif (userHasPrivilege($user, "user-copy-files")) {
+ $user_shell = "/usr/local/bin/scponly";
+ } elseif (userHasPrivilege($user, "user-ssh-tunnel")) {
+ $user_shell = "/usr/local/sbin/ssh_tunnel_shell";
+ } elseif (userHasPrivilege($user, "user-ipsec-xauth-dialin")) {
+ $user_shell = "/sbin/nologin";
+ } else {
+ $user_shell = "/sbin/nologin";
+ $lock_account = true;
+ }
+
+ /* Lock out disabled or expired users, unless it's root/admin. */
+ if ((is_account_disabled($user_name) || is_account_expired($user_name)) && ($user_uid != 0)) {
+ $user_shell = "/sbin/nologin";
+ $lock_account = true;
+ }
+
+ /* root user special handling */
+ if ($user_uid == 0) {
+ $cmd = "/usr/sbin/pw usermod -q -n root -s /bin/sh -H 0";
+ if ($debug) {
+ log_error(sprintf(gettext("Running: %s"), $cmd));
+ }
+ $fd = popen($cmd, "w");
+ fwrite($fd, $user['password']);
+ pclose($fd);
+ $user_group = "wheel";
+ $user_home = "/root";
+ $user_shell = "/etc/rc.initial";
+ }
+
+ /* read from pw db */
+ $fd = popen("/usr/sbin/pw usershow -n {$user_name} 2>&1", "r");
+ $pwread = fgets($fd);
+ pclose($fd);
+ $userattrs = explode(":", trim($pwread));
+
+ /* determine add or mod */
+ if (($userattrs[0] != $user['name']) || (!strncmp($pwread, "pw:", 3))) {
+ $user_op = "useradd -m -k /etc/skel -o";
+ } else {
+ $user_op = "usermod";
+ }
+
+ $comment = str_replace(array(":", "!", "@"), " ", $user['descr']);
+ /* add or mod pw db */
+ $cmd = "/usr/sbin/pw {$user_op} -q -u {$user_uid} -n {$user_name}".
+ " -g {$user_group} -s {$user_shell} -d {$user_home}".
+ " -c ".escapeshellarg($comment)." -H 0 2>&1";
+
+ if ($debug) {
+ log_error(sprintf(gettext("Running: %s"), $cmd));
+ }
+ $fd = popen($cmd, "w");
+ fwrite($fd, $user['password']);
+ pclose($fd);
+
+ /* create user directory if required */
+ if (!is_dir($user_home)) {
+ mkdir($user_home, 0700);
+ }
+ @chown($user_home, $user_name);
+ @chgrp($user_home, $user_group);
+
+ /* write out ssh authorized key file */
+ if ($user['authorizedkeys']) {
+ if (!is_dir("{$user_home}/.ssh")) {
+ @mkdir("{$user_home}/.ssh", 0700);
+ @chown("{$user_home}/.ssh", $user_name);
+ }
+ $keys = base64_decode($user['authorizedkeys']);
+ @file_put_contents("{$user_home}/.ssh/authorized_keys", $keys);
+ @chown("{$user_home}/.ssh/authorized_keys", $user_name);
+ } else {
+ unlink_if_exists("{$user_home}/.ssh/authorized_keys");
+ }
+
+ $un = $lock_account ? "" : "un";
+ exec("/usr/sbin/pw {$un}lock {$user_name} -q");
+
+ conf_mount_ro();
+}
+
+function local_user_del($user) {
+ global $debug;
+
+ /* remove all memberships */
+ local_user_set_groups($user);
+
+ /* Don't remove /root */
+ if ($user['uid'] != 0) {
+ $rmhome = "-r";
+ }
+
+ /* read from pw db */
+ $fd = popen("/usr/sbin/pw usershow -n {$user['name']} 2>&1", "r");
+ $pwread = fgets($fd);
+ pclose($fd);
+ $userattrs = explode(":", trim($pwread));
+
+ if ($userattrs[0] != $user['name']) {
+ log_error("Tried to remove user {$user['name']} but got user {$userattrs[0]} instead. Bailing.");
+ return;
+ }
+
+ /* delete from pw db */
+ $cmd = "/usr/sbin/pw userdel -n {$user['name']} {$rmhome}";
+
+ if ($debug) {
+ log_error(sprintf(gettext("Running: %s"), $cmd));
+ }
+ mwexec($cmd);
+
+ /* Delete user from groups needs a call to write_config() */
+ local_group_del_user($user);
+}
+
+function local_user_set_password(&$user, $password) {
+
+ $user['password'] = crypt($password);
+ $user['md5-hash'] = md5($password);
+
+ // Converts ascii to unicode.
+ $astr = (string) $password;
+ $ustr = '';
+ for ($i = 0; $i < strlen($astr); $i++) {
+ $a = ord($astr{$i}) << 8;
+ $ustr .= sprintf("%X", $a);
+ }
+
+}
+
+function local_user_get_groups($user, $all = false) {
+ global $debug, $config;
+
+ $groups = array();
+ if (!is_array($config['system']['group'])) {
+ return $groups;
+ }
+
+ foreach ($config['system']['group'] as $group) {
+ if ($all || (!$all && ($group['name'] != "all"))) {
+ if (is_array($group['member'])) {
+ if (in_array($user['uid'], $group['member'])) {
+ $groups[] = $group['name'];
+ }
+ }
+ }
+ }
+
+ if ($all) {
+ $groups[] = "all";
+ }
+
+ sort($groups);
+
+ return $groups;
+
+}
+
+function local_user_set_groups($user, $new_groups = NULL) {
+ global $debug, $config, $groupindex;
+
+ if (!is_array($config['system']['group'])) {
+ return;
+ }
+
+ $cur_groups = local_user_get_groups($user, true);
+ $mod_groups = array();
+
+ if (!is_array($new_groups)) {
+ $new_groups = array();
+ }
+
+ if (!is_array($cur_groups)) {
+ $cur_groups = array();
+ }
+
+ /* determine which memberships to add */
+ foreach ($new_groups as $groupname) {
+ if ($groupname == '' || in_array($groupname, $cur_groups)) {
+ continue;
+ }
+ $group = & $config['system']['group'][$groupindex[$groupname]];
+ $group['member'][] = $user['uid'];
+ $mod_groups[] = $group;
+ }
+ unset($group);
+
+ /* determine which memberships to remove */
+ foreach ($cur_groups as $groupname) {
+ if (in_array($groupname, $new_groups)) {
+ continue;
+ }
+ if (!isset($config['system']['group'][$groupindex[$groupname]])) {
+ continue;
+ }
+ $group = & $config['system']['group'][$groupindex[$groupname]];
+ if (is_array($group['member'])) {
+ $index = array_search($user['uid'], $group['member']);
+ array_splice($group['member'], $index, 1);
+ $mod_groups[] = $group;
+ }
+ }
+ unset($group);
+
+ /* sync all modified groups */
+ foreach ($mod_groups as $group) {
+ local_group_set($group);
+ }
+}
+
+function local_group_del_user($user) {
+ global $config;
+
+ if (!is_array($config['system']['group'])) {
+ return;
+ }
+
+ foreach ($config['system']['group'] as $group) {
+ if (is_array($group['member'])) {
+ foreach ($group['member'] as $idx => $uid) {
+ if ($user['uid'] == $uid) {
+ unset($config['system']['group']['member'][$idx]);
+ }
+ }
+ }
+ }
+}
+
+function local_group_set($group, $reset = false) {
+ global $debug;
+
+ $group_name = $group['name'];
+ $group_gid = $group['gid'];
+ $group_members = '';
+ if (!$reset && !empty($group['member']) && count($group['member']) > 0) {
+ $group_members = implode(",", $group['member']);
+ }
+
+ if (empty($group_name)) {
+ return;
+ }
+
+ /* read from group db */
+ $fd = popen("/usr/sbin/pw groupshow {$group_name} 2>&1", "r");
+ $pwread = fgets($fd);
+ pclose($fd);
+
+ /* determine add or mod */
+ if (!strncmp($pwread, "pw:", 3)) {
+ $group_op = "groupadd";
+ } else {
+ $group_op = "groupmod";
+ }
+
+ /* add or mod group db */
+ $cmd = "/usr/sbin/pw {$group_op} {$group_name} -g {$group_gid} -M '{$group_members}' 2>&1";
+
+ if ($debug) {
+ log_error(sprintf(gettext("Running: %s"), $cmd));
+ }
+ mwexec($cmd);
+
+}
+
+function local_group_del($group) {
+ global $debug;
+
+ /* delete from group db */
+ $cmd = "/usr/sbin/pw groupdel {$group['name']}";
+
+ if ($debug) {
+ log_error(sprintf(gettext("Running: %s"), $cmd));
+ }
+ mwexec($cmd);
+}
+
+function ldap_test_connection($authcfg) {
+ global $debug, $config, $g;
+
+ if ($authcfg) {
+ if (strstr($authcfg['ldap_urltype'], "Standard")) {
+ $ldapproto = "ldap";
+ } else {
+ $ldapproto = "ldaps";
+ }
+ $ldapserver = "{$ldapproto}://" . ldap_format_host($authcfg['host']);
+ $ldapport = $authcfg['ldap_port'];
+ if (!empty($ldapport)) {
+ $ldapserver .= ":{$ldapport}";
+ }
+ $ldapbasedn = $authcfg['ldap_basedn'];
+ $ldapbindun = $authcfg['ldap_binddn'];
+ $ldapbindpw = $authcfg['ldap_bindpw'];
+ } else {
+ return false;
+ }
+
+ /* first check if there is even an LDAP server populated */
+ if (!$ldapserver) {
+ return false;
+ }
+
+ /* Setup CA environment if needed. */
+ ldap_setup_caenv($authcfg);
+
+ /* connect and see if server is up */
+ $error = false;
+ if (!($ldap = ldap_connect($ldapserver))) {
+ $error = true;
+ }
+
+ if ($error == true) {
+ log_error(sprintf(gettext("ERROR! Could not connect to server %s."), $ldapname));
+ return false;
+ }
+
+ return true;
+}
+
+function ldap_setup_caenv($authcfg) {
+ global $g;
+ require_once("certs.inc");
+
+ unset($caref);
+ if (empty($authcfg['ldap_caref']) || !strstr($authcfg['ldap_urltype'], "SSL")) {
+ putenv('LDAPTLS_REQCERT=never');
+ return;
+ } else {
+ $caref = lookup_ca($authcfg['ldap_caref']);
+ if (!$caref) {
+ log_error(sprintf(gettext("LDAP: Could not lookup CA by reference for host %s."), $authcfg['ldap_caref']));
+ /* XXX: Prevent for credential leaking since we cannot setup the CA env. Better way? */
+ putenv('LDAPTLS_REQCERT=hard');
+ return;
+ }
+ if (!is_dir("{$g['varrun_path']}/certs")) {
+ @mkdir("{$g['varrun_path']}/certs");
+ }
+ if (file_exists("{$g['varrun_path']}/certs/{$caref['refid']}.ca")) {
+ @unlink("{$g['varrun_path']}/certs/{$caref['refid']}.ca");
+ }
+ file_put_contents("{$g['varrun_path']}/certs/{$caref['refid']}.ca", base64_decode($caref['crt']));
+ @chmod("{$g['varrun_path']}/certs/{$caref['refid']}.ca", 0600);
+ putenv('LDAPTLS_REQCERT=hard');
+ /* XXX: Probably even the hashed link should be created for this? */
+ putenv("LDAPTLS_CACERTDIR={$g['varrun_path']}/certs");
+ putenv("LDAPTLS_CACERT={$g['varrun_path']}/certs/{$caref['refid']}.ca");
+ }
+}
+
+function ldap_test_bind($authcfg) {
+ global $debug, $config, $g;
+
+ if ($authcfg) {
+ if (strstr($authcfg['ldap_urltype'], "Standard")) {
+ $ldapproto = "ldap";
+ } else {
+ $ldapproto = "ldaps";
+ }
+ $ldapserver = "{$ldapproto}://" . ldap_format_host($authcfg['host']);
+ $ldapport = $authcfg['ldap_port'];
+ if (!empty($ldapport)) {
+ $ldapserver .= ":{$ldapport}";
+ }
+ $ldapbasedn = $authcfg['ldap_basedn'];
+ $ldapbindun = $authcfg['ldap_binddn'];
+ $ldapbindpw = $authcfg['ldap_bindpw'];
+ $ldapver = $authcfg['ldap_protver'];
+ if (empty($ldapbndun) || empty($ldapbindpw)) {
+ $ldapanon = true;
+ } else {
+ $ldapanon = false;
+ }
+ } else {
+ return false;
+ }
+
+ /* first check if there is even an LDAP server populated */
+ if (!$ldapserver) {
+ return false;
+ }
+
+ /* Setup CA environment if needed. */
+ ldap_setup_caenv($authcfg);
+
+ /* connect and see if server is up */
+ $error = false;
+ if (!($ldap = ldap_connect($ldapserver))) {
+ $error = true;
+ }
+
+ if ($error == true) {
+ log_error(sprintf(gettext("ERROR! Could not connect to server %s."), $ldapname));
+ return false;
+ }
+
+ ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
+ ldap_set_option($ldap, LDAP_OPT_DEREF, LDAP_DEREF_SEARCHING);
+ ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, (int)$ldapver);
+
+ $ldapbindun = isset($authcfg['ldap_utf8']) ? utf8_encode($ldapbindun) : $ldapbindun;
+ $ldapbindpw = isset($authcfg['ldap_utf8']) ? utf8_encode($ldapbindpw) : $ldapbindpw;
+ if ($ldapanon == true) {
+ if (!($res = @ldap_bind($ldap))) {
+ @ldap_close($ldap);
+ return false;
+ }
+ } else if (!($res = @ldap_bind($ldap, $ldapbindun, $ldapbindpw))) {
+ @ldap_close($ldap);
+ return false;
+ }
+
+ @ldap_unbind($ldap);
+
+ return true;
+}
+
+function ldap_get_user_ous($show_complete_ou=true, $authcfg) {
+ global $debug, $config, $g;
+
+ if (!function_exists("ldap_connect")) {
+ return;
+ }
+
+ $ous = array();
+
+ if ($authcfg) {
+ if (strstr($authcfg['ldap_urltype'], "Standard")) {
+ $ldapproto = "ldap";
+ } else {
+ $ldapproto = "ldaps";
+ }
+ $ldapserver = "{$ldapproto}://" . ldap_format_host($authcfg['host']);
+ $ldapport = $authcfg['ldap_port'];
+ if (!empty($ldapport)) {
+ $ldapserver .= ":{$ldapport}";
+ }
+ $ldapbasedn = $authcfg['ldap_basedn'];
+ $ldapbindun = $authcfg['ldap_binddn'];
+ $ldapbindpw = $authcfg['ldap_bindpw'];
+ $ldapver = $authcfg['ldap_protver'];
+ if (empty($ldapbindun) || empty($ldapbindpw)) {
+ $ldapanon = true;
+ } else {
+ $ldapanon = false;
+ }
+ $ldapname = $authcfg['name'];
+ $ldapfallback = false;
+ $ldapscope = $authcfg['ldap_scope'];
+ } else {
+ return false;
+ }
+
+ /* first check if there is even an LDAP server populated */
+ if (!$ldapserver) {
+ log_error(gettext("ERROR! ldap_get_user_ous() backed selected with no LDAP authentication server defined."));
+ return $ous;
+ }
+
+ /* Setup CA environment if needed. */
+ ldap_setup_caenv($authcfg);
+
+ /* connect and see if server is up */
+ $error = false;
+ if (!($ldap = ldap_connect($ldapserver))) {
+ $error = true;
+ }
+
+ if ($error == true) {
+ log_error(sprintf(gettext("ERROR! Could not connect to server %s."), $ldapname));
+ return $ous;
+ }
+
+ $ldapfilter = "(|(ou=*)(cn=Users))";
+
+ ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
+ ldap_set_option($ldap, LDAP_OPT_DEREF, LDAP_DEREF_SEARCHING);
+ ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, (int)$ldapver);
+
+ $ldapbindun = isset($authcfg['ldap_utf8']) ? utf8_encode($ldapbindun) : $ldapbindun;
+ $ldapbindpw = isset($authcfg['ldap_utf8']) ? utf8_encode($ldapbindpw) : $ldapbindpw;
+ if ($ldapanon == true) {
+ if (!($res = @ldap_bind($ldap))) {
+ log_error(sprintf(gettext("ERROR! ldap_get_user_ous() could not bind anonymously to server %s."), $ldapname));
+ @ldap_close($ldap);
+ return $ous;
+ }
+ } else if (!($res = @ldap_bind($ldap, $ldapbindun, $ldapbindpw))) {
+ log_error(sprintf(gettext("ERROR! ldap_get_user_ous() could not bind to server %s."), $ldapname));
+ @ldap_close($ldap);
+ return $ous;
+ }
+
+ if ($ldapscope == "one") {
+ $ldapfunc = "ldap_list";
+ } else {
+ $ldapfunc = "ldap_search";
+ }
+
+ $search = @$ldapfunc($ldap, $ldapbasedn, $ldapfilter);
+ $info = @ldap_get_entries($ldap, $search);
+
+ if (is_array($info)) {
+ foreach ($info as $inf) {
+ if (!$show_complete_ou) {
+ $inf_split = explode(",", $inf['dn']);
+ $ou = $inf_split[0];
+ $ou = str_replace("OU=", "", $ou);
+ $ou = str_replace("CN=", "", $ou);
+ } else {
+ if ($inf['dn']) {
+ $ou = $inf['dn'];
+ }
+ }
+ if ($ou) {
+ $ous[] = $ou;
+ }
+ }
+ }
+
+ @ldap_unbind($ldap);
+
+ return $ous;
+}
+
+function ldap_get_groups($username, $authcfg) {
+ global $debug, $config;
+
+ if (!function_exists("ldap_connect")) {
+ return;
+ }
+
+ if (!$username) {
+ return false;
+ }
+
+ if (!isset($authcfg['ldap_nostrip_at']) && stristr($username, "@")) {
+ $username_split = explode("@", $username);
+ $username = $username_split[0];
+ }
+
+ if (stristr($username, "\\")) {
+ $username_split = explode("\\", $username);
+ $username = $username_split[0];
+ }
+
+ //log_error("Getting LDAP groups for {$username}.");
+ if ($authcfg) {
+ if (strstr($authcfg['ldap_urltype'], "Standard")) {
+ $ldapproto = "ldap";
+ } else {
+ $ldapproto = "ldaps";
+ }
+ $ldapserver = "{$ldapproto}://" . ldap_format_host($authcfg['host']);
+ $ldapport = $authcfg['ldap_port'];
+ if (!empty($ldapport)) {
+ $ldapserver .= ":{$ldapport}";
+ }
+ $ldapbasedn = $authcfg['ldap_basedn'];
+ $ldapbindun = $authcfg['ldap_binddn'];
+ $ldapbindpw = $authcfg['ldap_bindpw'];
+ $ldapauthcont = $authcfg['ldap_authcn'];
+ $ldapnameattribute = strtolower($authcfg['ldap_attr_user']);
+ $ldapgroupattribute = strtolower($authcfg['ldap_attr_member']);
+ $ldapfilter = "({$ldapnameattribute}={$username})";
+ $ldaptype = "";
+ $ldapver = $authcfg['ldap_protver'];
+ if (empty($ldapbindun) || empty($ldapbindpw)) {
+ $ldapanon = true;
+ } else {
+ $ldapanon = false;
+ }
+ $ldapname = $authcfg['name'];
+ $ldapfallback = false;
+ $ldapscope = $authcfg['ldap_scope'];
+ } else {
+ return false;
+ }
+
+ $ldapdn = $_SESSION['ldapdn'];
+
+ /*Convert attribute to lowercase. php ldap arrays put everything in lowercase */
+ $ldapgroupattribute = strtolower($ldapgroupattribute);
+ $memberof = array();
+
+ /* Setup CA environment if needed. */
+ ldap_setup_caenv($authcfg);
+
+ /* connect and see if server is up */
+ $error = false;
+ if (!($ldap = ldap_connect($ldapserver))) {
+ $error = true;
+ }
+
+ if ($error == true) {
+ log_error(sprintf(gettext("ERROR! ldap_get_groups() Could not connect to server %s."), $ldapname));
+ return memberof;
+ }
+
+ ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
+ ldap_set_option($ldap, LDAP_OPT_DEREF, LDAP_DEREF_SEARCHING);
+ ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, (int)$ldapver);
+
+ /* bind as user that has rights to read group attributes */
+ $ldapbindun = isset($authcfg['ldap_utf8']) ? utf8_encode($ldapbindun) : $ldapbindun;
+ $ldapbindpw = isset($authcfg['ldap_utf8']) ? utf8_encode($ldapbindpw) : $ldapbindpw;
+ if ($ldapanon == true) {
+ if (!($res = @ldap_bind($ldap))) {
+ log_error(sprintf(gettext("ERROR! ldap_get_groups() could not bind anonymously to server %s."), $ldapname));
+ @ldap_close($ldap);
+ return false;
+ }
+ } else if (!($res = @ldap_bind($ldap, $ldapbindun, $ldapbindpw))) {
+ log_error(sprintf(gettext("ERROR! ldap_get_groups() could not bind to server %s."), $ldapname));
+ @ldap_close($ldap);
+ return memberof;
+ }
+
+ /* get groups from DN found */
+ /* use ldap_read instead of search so we don't have to do a bunch of extra work */
+ /* since we know the DN is in $_SESSION['ldapdn'] */
+ //$search = ldap_read($ldap, $ldapdn, "(objectclass=*)", array($ldapgroupattribute));
+ if ($ldapscope == "one") {
+ $ldapfunc = "ldap_list";
+ } else {
+ $ldapfunc = "ldap_search";
+ }
+
+ $search = @$ldapfunc($ldap, $ldapdn, $ldapfilter, array($ldapgroupattribute));
+ $info = @ldap_get_entries($ldap, $search);
+
+ $countem = $info["count"];
+
+ if (is_array($info[0][$ldapgroupattribute])) {
+ /* Iterate through the groups and throw them into an array */
+ foreach ($info[0][$ldapgroupattribute] as $member) {
+ if (stristr($member, "CN=") !== false) {
+ $membersplit = explode(",", $member);
+ $memberof[] = preg_replace("/CN=/i", "", $membersplit[0]);
+ }
+ }
+ }
+
+ /* Time to close LDAP connection */
+ @ldap_unbind($ldap);
+
+ $groups = print_r($memberof, true);
+
+ //log_error("Returning groups ".$groups." for user $username");
+
+ return $memberof;
+}
+
+function ldap_format_host($host) {
+ return is_ipaddrv6($host) ? "[$host]" : $host ;
+}
+
+function ldap_backed($username, $passwd, $authcfg) {
+ global $debug, $config;
+
+ if (!$username) {
+ return;
+ }
+
+ if (!function_exists("ldap_connect")) {
+ return;
+ }
+
+ if (!isset($authcfg['ldap_nostrip_at']) && stristr($username, "@")) {
+ $username_split = explode("@", $username);
+ $username = $username_split[0];
+ }
+ if (stristr($username, "\\")) {
+ $username_split = explode("\\", $username);
+ $username = $username_split[0];
+ }
+
+ if ($authcfg) {
+ if (strstr($authcfg['ldap_urltype'], "Standard")) {
+ $ldapproto = "ldap";
+ } else {
+ $ldapproto = "ldaps";
+ }
+ $ldapserver = "{$ldapproto}://" . ldap_format_host($authcfg['host']);
+ $ldapport = $authcfg['ldap_port'];
+ if (!empty($ldapport)) {
+ $ldapserver .= ":{$ldapport}";
+ }
+ $ldapbasedn = $authcfg['ldap_basedn'];
+ $ldapbindun = $authcfg['ldap_binddn'];
+ $ldapbindpw = $authcfg['ldap_bindpw'];
+ if (empty($ldapbindun) || empty($ldapbindpw)) {
+ $ldapanon = true;
+ } else {
+ $ldapanon = false;
+ }
+ $ldapauthcont = $authcfg['ldap_authcn'];
+ $ldapnameattribute = strtolower($authcfg['ldap_attr_user']);
+ $ldapextendedqueryenabled = $authcfg['ldap_extended_enabled'];
+ $ldapextendedquery = $authcfg['ldap_extended_query'];
+ $ldapfilter = "";
+ if (!$ldapextendedqueryenabled) {
+ $ldapfilter = "({$ldapnameattribute}={$username})";
+ } else {
+ $ldapfilter = "(&({$ldapnameattribute}={$username})({$ldapextendedquery}))";
+ }
+ $ldaptype = "";
+ $ldapver = $authcfg['ldap_protver'];
+ $ldapname = $authcfg['name'];
+ $ldapscope = $authcfg['ldap_scope'];
+ } else {
+ return false;
+ }
+
+ /* first check if there is even an LDAP server populated */
+ if (!$ldapserver) {
+ if ($ldapfallback) {
+ log_error(gettext("ERROR! ldap_backed() called with no LDAP authentication server defined. Defaulting to local user database. Visit System -> User Manager."));
+ return local_backed($username, $passwd);
+ } else {
+ log_error(gettext("ERROR! ldap_backed() called with no LDAP authentication server defined."));
+ }
+
+ return false;
+ }
+
+ /* Setup CA environment if needed. */
+ ldap_setup_caenv($authcfg);
+
+ ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
+ ldap_set_option($ldap, LDAP_OPT_DEREF, LDAP_DEREF_SEARCHING);
+ ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, (int)$ldapver);
+
+ /* Make sure we can connect to LDAP */
+ $error = false;
+ if (!($ldap = ldap_connect($ldapserver))) {
+ $error = true;
+ }
+
+ if ($error == true) {
+ log_error(sprintf(gettext("ERROR! Could not connect to server %s."), $ldapname));
+ return false;
+ }
+
+ /* ok, its up. now, lets bind as the bind user so we can search it */
+ $error = false;
+ $ldapbindun = isset($authcfg['ldap_utf8']) ? utf8_encode($ldapbindun) : $ldapbindun;
+ $ldapbindpw = isset($authcfg['ldap_utf8']) ? utf8_encode($ldapbindpw) : $ldapbindpw;
+ if ($ldapanon == true) {
+ if (!($res = @ldap_bind($ldap))) {
+ $error = true;
+ }
+ } else if (!($res = @ldap_bind($ldap, $ldapbindun, $ldapbindpw))) {
+ $error = true;
+ }
+
+ if ($error == true) {
+ @ldap_close($ldap);
+ log_error(sprintf(gettext("ERROR! Could not bind to server %s."), $ldapname));
+ return false;
+ }
+
+ /* Get LDAP Authcontainers and split em up. */
+ $ldac_splits = explode(";", $ldapauthcont);
+
+ /* setup the usercount so we think we haven't found anyone yet */
+ $usercount = 0;
+
+ /*****************************************************************/
+ /* We First find the user based on username and filter */
+ /* Then, once we find the first occurrence of that person */
+ /* We set session variables to point to the OU and DN of the */
+ /* Person. To later be used by ldap_get_groups. */
+ /* that way we don't have to search twice. */
+ /*****************************************************************/
+ if ($debug) {
+ log_auth(sprintf(gettext("Now Searching for %s in directory."), $username));
+ }
+ /* Iterate through the user containers for search */
+ foreach ($ldac_splits as $i => $ldac_split) {
+ $ldac_split = isset($authcfg['ldap_utf8']) ? utf8_encode($ldac_split) : $ldac_split;
+ $ldapfilter = isset($authcfg['ldap_utf8']) ? utf8_encode($ldapfilter) : $ldapfilter;
+ $ldapsearchbasedn = isset($authcfg['ldap_utf8']) ? utf8_encode("{$ldac_split},{$ldapbasedn}") : "{$ldac_split},{$ldapbasedn}";
+ /* Make sure we just use the first user we find */
+ if ($debug) {
+ log_auth(sprintf(gettext('Now Searching in server %1$s, container %2$s with filter %3$s.'), $ldapname, utf8_decode($ldac_split), utf8_decode($ldapfilter)));
+ }
+ if ($ldapscope == "one") {
+ $ldapfunc = "ldap_list";
+ } else {
+ $ldapfunc = "ldap_search";
+ }
+ /* Support legacy auth container specification. */
+ if (stristr($ldac_split, "DC=") || empty($ldapbasedn)) {
+ $search = @$ldapfunc($ldap,$ldac_split,$ldapfilter);
+ } else {
+ $search = @$ldapfunc($ldap,$ldapsearchbasedn,$ldapfilter);
+ }
+ if (!$search) {
+ log_error(sprintf(gettext("Search resulted in error: %s"), ldap_error($ldap)));
+ continue;
+ }
+ $info = ldap_get_entries($ldap, $search);
+ $matches = $info['count'];
+ if ($matches == 1) {
+ $userdn = $_SESSION['ldapdn'] = $info[0]['dn'];
+ $_SESSION['ldapou'] = $ldac_split[$i];
+ $_SESSION['ldapon'] = "true";
+ $usercount = 1;
+ break;
+ }
+ }
+
+ if ($usercount != 1) {
+ @ldap_unbind($ldap);
+ log_error(gettext("ERROR! Either LDAP search failed, or multiple users were found."));
+ return false;
+ }
+
+ /* Now lets bind as the user we found */
+ $passwd = isset($authcfg['ldap_utf8']) ? utf8_encode($passwd) : $passwd;
+ if (!($res = @ldap_bind($ldap, $userdn, $passwd))) {
+ log_error(sprintf(gettext('ERROR! Could not login to server %1$s as user %2$s: %3$s'), $ldapname, $username, ldap_error($ldap)));
+ @ldap_unbind($ldap);
+ return false;
+ }
+
+ if ($debug) {
+ $userdn = isset($authcfg['ldap_utf8']) ? utf8_decode($userdn) : $userdn;
+ log_auth(sprintf(gettext('Logged in successfully as %1$s via LDAP server %2$s with DN = %3$s.'), $username, $ldapname, $userdn));
+ }
+
+ /* At this point we are bound to LDAP so the user was auth'd okay. Close connection. */
+ @ldap_unbind($ldap);
+
+ return true;
+}
+
+function radius_backed($username, $passwd, $authcfg, &$attributes = array()) {
+ global $debug, $config;
+ $ret = false;
+
+ require_once("radius.inc");
+
+ $rauth = new Auth_RADIUS_PAP($username, $passwd);
+ if ($authcfg) {
+ $radiusservers = array();
+ $radiusservers[0]['ipaddr'] = $authcfg['host'];
+ $radiusservers[0]['port'] = $authcfg['radius_auth_port'];
+ $radiusservers[0]['sharedsecret'] = $authcfg['radius_secret'];
+ $radiusservers[0]['timeout'] = $authcfg['radius_timeout'];
+ } else {
+ return false;
+ }
+
+ /* Add new servers to our instance */
+ foreach ($radiusservers as $radsrv) {
+ $timeout = (is_numeric($radsrv['timeout'])) ? $radsrv['timeout'] : 5;
+ $rauth->addServer($radsrv['ipaddr'], $radsrv['port'], $radsrv['sharedsecret'], $timeout);
+ }
+
+ if (PEAR::isError($rauth->start())) {
+ $retvalue['auth_val'] = 1;
+ $retvalue['error'] = $rauth->getError();
+ if ($debug) {
+ printf(gettext("Radius start: %s<br />\n"), $retvalue['error']);
+ }
+ }
+
+ // XXX - billm - somewhere in here we need to handle securid challenge/response
+
+ /* Send request */
+ $result = $rauth->send();
+ if (PEAR::isError($result)) {
+ $retvalue['auth_val'] = 1;
+ $retvalue['error'] = $result->getMessage();
+ if ($debug) {
+ printf(gettext("Radius send failed: %s<br />\n"), $retvalue['error']);
+ }
+ } else if ($result === true) {
+ if ($rauth->getAttributes()) {
+ $attributes = $rauth->listAttributes();
+ }
+ $retvalue['auth_val'] = 2;
+ if ($debug) {
+ printf(gettext("Radius Auth succeeded")."<br />\n");
+ }
+ $ret = true;
+ } else {
+ $retvalue['auth_val'] = 3;
+ if ($debug) {
+ printf(gettext("Radius Auth rejected")."<br />\n");
+ }
+ }
+
+ // close OO RADIUS_AUTHENTICATION
+ $rauth->close();
+
+ return $ret;
+}
+
+/*
+ $attributes must contain a "class" key containing the groups and local
+ groups must exist to match.
+*/
+function radius_get_groups($attributes) {
+ $groups = array();
+ if (!empty($attributes) && is_array($attributes) && !empty($attributes['class'])) {
+ $groups = explode(";", $attributes['class']);
+ foreach ($groups as & $grp) {
+ $grp = trim($grp);
+ if (strtolower(substr($grp, 0, 3)) == "ou=") {
+ $grp = substr($grp, 3);
+ }
+ }
+ }
+ return $groups;
+}
+
+function get_user_expiration_date($username) {
+ $user = getUserEntry($username);
+ if ($user['expires']) {
+ return $user['expires'];
+ }
+}
+
+function is_account_expired($username) {
+ $expirydate = get_user_expiration_date($username);
+ if ($expirydate) {
+ if (strtotime("-1 day") > strtotime(date("m/d/Y", strtotime($expirydate)))) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function is_account_disabled($username) {
+ $user = getUserEntry($username);
+ if (isset($user['disabled'])) {
+ return true;
+ }
+
+ return false;
+}
+
+function auth_get_authserver($name) {
+ global $config;
+
+ if (is_array($config['system']['authserver'])) {
+ foreach ($config['system']['authserver'] as $authcfg) {
+ if ($authcfg['name'] == $name) {
+ return $authcfg;
+ }
+ }
+ }
+ if ($name == "Local Database") {
+ return array("name" => gettext("Local Database"), "type" => "Local Auth", "host" => $config['system']['hostname']);
+ }
+}
+
+function auth_get_authserver_list() {
+ global $config;
+
+ $list = array();
+
+ if (is_array($config['system']['authserver'])) {
+ foreach ($config['system']['authserver'] as $authcfg) {
+ /* Add support for disabled entries? */
+ $list[$authcfg['name']] = $authcfg;
+ }
+ }
+
+ $list["Local Database"] = array("name" => gettext("Local Database"), "type" => "Local Auth", "host" => $config['system']['hostname']);
+ return $list;
+}
+
+function getUserGroups($username, $authcfg, &$attributes = array()) {
+ global $config;
+
+ $allowed_groups = array();
+
+ switch ($authcfg['type']) {
+ case 'ldap':
+ $allowed_groups = @ldap_get_groups($username, $authcfg);
+ break;
+ case 'radius':
+ $allowed_groups = @radius_get_groups($attributes);
+ break;
+ default:
+ $user = getUserEntry($username);
+ $allowed_groups = @local_user_get_groups($user, true);
+ break;
+ }
+
+ $member_groups = array();
+ if (is_array($config['system']['group'])) {
+ foreach ($config['system']['group'] as $group) {
+ if (in_array($group['name'], $allowed_groups)) {
+ $member_groups[] = $group['name'];
+ }
+ }
+ }
+
+ return $member_groups;
+}
+
+function authenticate_user($username, $password, $authcfg = NULL, &$attributes = array()) {
+
+ if (!$authcfg) {
+ return local_backed($username, $password);
+ }
+
+ $authenticated = false;
+ switch ($authcfg['type']) {
+ case 'ldap':
+ if (ldap_backed($username, $password, $authcfg)) {
+ $authenticated = true;
+ }
+ break;
+ case 'radius':
+ if (radius_backed($username, $password, $authcfg, $attributes)) {
+ $authenticated = true;
+ }
+ break;
+ default:
+ /* lookup user object by name */
+ if (local_backed($username, $password)) {
+ $authenticated = true;
+ }
+ break;
+ }
+
+ return $authenticated;
+}
+
+function session_auth() {
+ global $config, $_SESSION, $page;
+
+ // Handle HTTPS httponly and secure flags
+ $currentCookieParams = session_get_cookie_params();
+ session_set_cookie_params(
+ $currentCookieParams["lifetime"],
+ $currentCookieParams["path"],
+ NULL,
+ ($config['system']['webgui']['protocol'] == "https"),
+ true
+ );
+
+ if (!session_id()) {
+ session_start();
+ }
+
+ // Detect protocol change
+ if (!isset($_POST['login']) && !empty($_SESSION['Logged_In']) && $_SESSION['protocol'] != $config['system']['webgui']['protocol']) {
+ return false;
+ }
+
+ /* Validate incoming login request */
+ $attributes = array();
+ if (isset($_POST['login']) && !empty($_POST['usernamefld']) && !empty($_POST['passwordfld'])) {
+ $authcfg = auth_get_authserver($config['system']['webgui']['authmode']);
+ if (authenticate_user($_POST['usernamefld'], $_POST['passwordfld'], $authcfg, $attributes) ||
+ authenticate_user($_POST['usernamefld'], $_POST['passwordfld'])) {
+ // Generate a new id to avoid session fixation
+ session_regenerate_id();
+ $_SESSION['Logged_In'] = "True";
+ $_SESSION['Username'] = $_POST['usernamefld'];
+ $_SESSION['user_radius_attributes'] = $attributes;
+ $_SESSION['last_access'] = time();
+ $_SESSION['protocol'] = $config['system']['webgui']['protocol'];
+ if (!isset($config['system']['webgui']['quietlogin'])) {
+ log_auth(sprintf(gettext("Successful login for user '%1\$s' from: %2\$s"), $_POST['usernamefld'], $_SERVER['REMOTE_ADDR']));
+ }
+ if (isset($_POST['postafterlogin'])) {
+ return true;
+ } else {
+ if (empty($page)) {
+ $page = "/";
+ }
+ header("Location: {$page}");
+ }
+ exit;
+ } else {
+ /* give the user an error message */
+ $_SESSION['Login_Error'] = "Username or Password incorrect";
+ log_auth("webConfigurator authentication error for '{$_POST['usernamefld']}' from {$_SERVER['REMOTE_ADDR']}");
+ if (isAjax()) {
+ echo "showajaxmessage('{$_SESSION['Login_Error']}');";
+ return;
+ }
+ }
+ }
+
+ /* Show login page if they aren't logged in */
+ if (empty($_SESSION['Logged_In'])) {
+ return false;
+ }
+
+ /* If session timeout isn't set, we don't mark sessions stale */
+ if (!isset($config['system']['webgui']['session_timeout'])) {
+ /* Default to 4 hour timeout if one is not set */
+ if ($_SESSION['last_access'] < (time() - 14400)) {
+ $_GET['logout'] = true;
+ $_SESSION['Logout'] = true;
+ } else {
+ $_SESSION['last_access'] = time();
+ }
+ } else if (intval($config['system']['webgui']['session_timeout']) == 0) {
+ /* only update if it wasn't ajax */
+ if (!isAjax()) {
+ $_SESSION['last_access'] = time();
+ }
+ } else {
+ /* Check for stale session */
+ if ($_SESSION['last_access'] < (time() - ($config['system']['webgui']['session_timeout'] * 60))) {
+ $_GET['logout'] = true;
+ $_SESSION['Logout'] = true;
+ } else {
+ /* only update if it wasn't ajax */
+ if (!isAjax()) {
+ $_SESSION['last_access'] = time();
+ }
+ }
+ }
+
+ /* user hit the logout button */
+ if (isset($_GET['logout'])) {
+
+ if ($_SESSION['Logout']) {
+ log_error(sprintf(gettext("Session timed out for user '%1\$s' from: %2\$s"), $_SESSION['Username'], $_SERVER['REMOTE_ADDR']));
+ } else {
+ log_error(sprintf(gettext("User logged out for user '%1\$s' from: %2\$s"), $_SESSION['Username'], $_SERVER['REMOTE_ADDR']));
+ }
+
+ /* wipe out $_SESSION */
+ $_SESSION = array();
+
+ if (isset($_COOKIE[session_name()])) {
+ setcookie(session_name(), '', time()-42000, '/');
+ }
+
+ /* and destroy it */
+ session_destroy();
+
+ $scriptName = explode("/", $_SERVER["SCRIPT_FILENAME"]);
+ $scriptElms = count($scriptName);
+ $scriptName = $scriptName[$scriptElms-1];
+
+ if (isAjax()) {
+ return false;
+ }
+
+ /* redirect to page the user is on, it'll prompt them to login again */
+ header("Location: {$scriptName}");
+
+ return false;
+ }
+
+ /*
+ * this is for debugging purpose if you do not want to use Ajax
+ * to submit a HTML form. It basically disables the observation
+ * of the submit event and hence does not trigger Ajax.
+ */
+ if ($_GET['disable_ajax']) {
+ $_SESSION['NO_AJAX'] = "True";
+ }
+
+ /*
+ * Same to re-enable Ajax.
+ */
+ if ($_GET['enable_ajax']) {
+ unset($_SESSION['NO_AJAX']);
+ }
+
+ return true;
+}
+
+?>
diff --git a/src/etc/inc/authgui.inc b/src/etc/inc/authgui.inc
new file mode 100644
index 0000000..07cf9a9
--- /dev/null
+++ b/src/etc/inc/authgui.inc
@@ -0,0 +1,335 @@
+<?php
+/* $Id$ */
+/*
+ Copyright (C) 2007, 2008 Scott Ullrich <sullrich@gmail.com>
+ All rights reserved.
+
+ Copyright (C) 2005-2006 Bill Marquette <bill.marquette@gmail.com>
+ All rights reserved.
+
+ Copyright (C) 2006 Paul Taylor <paultaylor@winn-dixie.com>.
+ All rights reserved.
+
+ Copyright (C) 2003-2006 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.
+
+ pfSense_MODULE: authgui
+*/
+
+include_once("auth.inc");
+include_once("priv.inc");
+if (!function_exists('platform_booting')) {
+ require_once('globals.inc');
+}
+
+/* Authenticate user - exit if failed */
+if (!session_auth()) {
+ display_login_form();
+ exit;
+}
+
+/*
+ * Once here, the user has authenticated with the web server.
+ * We give them access only to the appropriate pages based on
+ * the user or group privileges.
+ */
+$allowedpages = getAllowedPages($_SESSION['Username'], $_SESSION['user_radius_attributes']);
+
+/*
+ * redirect to first allowed page if requesting a wrong url
+ */
+if (!isAllowedPage($_SERVER['REQUEST_URI'])) {
+ if (count($allowedpages) > 0) {
+ $page = str_replace('*', '', $allowedpages[0]);
+ $_SESSION['Post_Login'] = true;
+ require_once("functions.inc");
+ pfSenseHeader("/{$page}");
+
+ $username = empty($_SESSION["Username"]) ? "(system)" : $_SESSION['Username'];
+ if (!empty($_SERVER['REMOTE_ADDR'])) {
+ $username .= '@' . $_SERVER['REMOTE_ADDR'];
+ }
+ log_error("{$username} attempted to access {$_SERVER['SCRIPT_NAME']} but does not have access to that page. Redirecting to {$page}.");
+
+ exit;
+ } else {
+ display_error_form("201", gettext("No page assigned to this user! Click here to logout."));
+ exit;
+ }
+} else {
+ $_SESSION['Post_Login'] = true;
+}
+
+/*
+ * redirect browsers post-login to avoid pages
+ * taking action in response to a POST request
+ */
+if (!$_SESSION['Post_Login']) {
+ $_SESSION['Post_Login'] = true;
+ require_once("functions.inc");
+ pfSenseHeader($_SERVER['REQUEST_URI']);
+ exit;
+}
+
+/*
+ * Close session data to allow other scripts from same host to come in.
+ * A session can be reactivated from calling session_start again
+ */
+session_commit();
+
+/*
+ * determine if the user is allowed access to the requested page
+ */
+function display_error_form($http_code, $desc) {
+ global $config, $g;
+ $g['theme'] = get_current_theme();
+ if (isAjax()) {
+ printf(gettext('Error: %1$s Description: %2$s'), $http_code, $desc);
+ return;
+ }
+
+?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+ <head>
+ <script type="text/javascript" src="/javascript/jquery-1.11.1.min.js"></script>
+ <script type="text/javascript" src="/javascript/jquery-migrate-1.2.1.min.js"></script>
+ <title><?=$http_code?></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <link rel="shortcut icon" href="/themes/<?= $g['theme'] ?>/images/icons/favicon.ico" />
+ <?php if (file_exists("{$g['www_path']}/themes/{$g['theme']}/login.css")): ?>
+ <link rel="stylesheet" type="text/css" href="/themes/<?= $g['theme'] ?>/login.css" media="all" />
+ <?php else: ?>
+ <link rel="stylesheet" type="text/css" href="/themes/<?= $g['theme'] ?>/all.css" media="all" />
+ <?php endif; ?>
+ <script type="text/javascript">
+ //<![CDATA[
+ function page_load() {}
+ function clearError() {
+ if ($('#inputerrors')) {
+ $('#inputerrors').html('');
+ }
+ }
+ <?php
+ require("headjs.php");
+ echo getHeadJS();
+ ?>
+ //]]>
+ </script>
+ <script type="text/javascript" src="/themes/<?= $g['theme'] ?>/javascript/niftyjsCode.js"></script>
+ </head>
+ <body onload="page_load();">
+ <div id="errordesc">
+ <h1>&nbsp</h1>
+ <a href="/index.php?logout">
+ <p id="errortext" style="vertical-align: middle; text-align: center;">
+ <span style="color: #000000; font-weight: bold;">
+ <?=$desc;?>
+ </span>
+ </p>
+ </div>
+ </body>
+</html>
+
+<?php
+
+} // end function
+
+
+function display_login_form() {
+ require_once("globals.inc");
+ global $config, $g;
+ $g['theme'] = get_current_theme();
+
+ unset($input_errors);
+
+ if (isAjax()) {
+ if (isset($_POST['login'])) {
+ if ($_SESSION['Logged_In'] <> "True") {
+ isset($_SESSION['Login_Error']) ? $login_error = $_SESSION['Login_Error'] : $login_error = gettext("unknown reason");
+ printf("showajaxmessage('" . gettext("Invalid login (%s).") . "')", $login_error);
+ }
+ if (file_exists("{$g['tmp_path']}/webconfigurator.lock")) {
+ // TODO: add the IP from the user who did lock the device
+ $whom = file_get_contents("{$g['tmp_path']}/webconfigurator.lock");
+ printf("showajaxmessage('" . gettext("This device is currently being maintained by: %s.") . "');", $whom);
+ }
+ }
+ exit;
+ }
+
+/* Check against locally configured IP addresses, which will catch when someone
+ port forwards WebGUI access from WAN to an internal IP on the router. */
+global $FilterIflist, $nifty_background;
+$local_ip = false;
+if (strpos($_SERVER['HTTP_HOST'], ":") === FALSE) {
+ $http_host_port = explode(":", $_SERVER['HTTP_HOST']);
+ $http_host = $http_host_port[0];
+} else {
+ $http_host = $_SERVER['HTTP_HOST'];
+}
+if (empty($FilterIflist)) {
+ require_once('filter.inc');
+ require_once('shaper.inc');
+ filter_generate_optcfg_array();
+}
+foreach ($FilterIflist as $iflist) {
+ if ($iflist['ip'] == $http_host) {
+ $local_ip = true;
+ } else if ($iflist['ipv6'] == $http_host) {
+ $local_ip = true;
+ } else if (is_array($iflist['vips'])) {
+ foreach ($iflist['vips'] as $vip) {
+ if ($vip['ip'] == $http_host) {
+ $local_ip = true;
+ break;
+ }
+ }
+ unset($vip);
+ }
+ if ($local_ip == true) {
+ break;
+ }
+}
+unset($FilterIflist);
+unset($iflist);
+
+if ($local_ip == false) {
+ if (is_array($config['openvpn']['openvpn-server'])) {
+ foreach ($config['openvpn']['openvpn-server'] as $ovpns) {
+ if (is_ipaddrv4($http_host) && !empty($ovpns['tunnel_network']) && ip_in_subnet($http_host, $ovpns['tunnel_network'])) {
+ $local_ip = true;
+ } else if (is_ipaddrv6($http_host) && !empty($ovpns['tunnel_networkv6']) && ip_in_subnet($http_host, $ovpns['tunnel_networkv6'])) {
+ $local_ip = true;
+ }
+ if ($local_ip == true) {
+ break;
+ }
+ }
+ }
+}
+
+?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+ <head>
+ <script type="text/javascript" src="/javascript/jquery-1.11.1.min.js"></script>
+ <script type="text/javascript" src="/javascript/jquery-migrate-1.2.1.min.js"></script>
+ <script type="text/javascript">
+ //<![CDATA[
+ $(document).ready(function() { jQuery('#usernamefld').focus(); });
+ //]]>
+ </script>
+
+ <title><?=gettext("Login"); ?></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <link rel="shortcut icon" href="/themes/<?= $g['theme'] ?>/images/icons/favicon.ico" />
+ <?php if (file_exists("{$g['www_path']}/themes/{$g['theme']}/login.css")): ?>
+ <link rel="stylesheet" type="text/css" href="/themes/<?= $g['theme'] ?>/login.css" media="all" />
+ <?php else: ?>
+ <link rel="stylesheet" type="text/css" href="/themes/<?= $g['theme'] ?>/all.css" media="all" />
+ <?php endif; ?>
+ <script type="text/javascript">
+ //<![CDATA[
+ function page_load() {}
+ function clearError() {
+ if ($('#inputerrors')) {
+ $('#inputerrors').html('');
+ }
+ }
+ <?php
+ require("headjs.php");
+ echo getHeadJS();
+ ?>
+ //]]>
+ </script>
+ <script type="text/javascript" src="/themes/<?= $g['theme'] ?>/javascript/niftyjsCode.js"></script>
+ </head>
+ <body onload="page_load()">
+ <div id="login">
+ <?php
+ if (is_ipaddr($http_host) && !$local_ip && !isset($config['system']['webgui']['nohttpreferercheck'])) {
+ $nifty_background = "#999";
+ print_info_box(gettext("You are accessing this router by an IP address not configured locally, which may be forwarded by NAT or other means. <br /><br />If you did not setup this forwarding, you may be the target of a man-in-the-middle attack."));
+ }
+ $loginautocomplete = isset($config['system']['webgui']['loginautocomplete']) ? '' : 'autocomplete="off"';
+ ?>
+ <form id="iform" name="iform" method="post" <?= $loginautocomplete ?> action="<?=$_SERVER['SCRIPT_NAME'];?>">
+ <h1>&nbsp;</h1>
+ <div id="inputerrors"><?=$_SESSION['Login_Error'];?></div>
+ <p>
+ <span style="text-align:left">
+ <?=gettext("Username:"); ?><br />
+ <input onclick="clearError();" onchange="clearError();" id="usernamefld" type="text" name="usernamefld" class="formfld user" tabindex="1" />
+ </span>
+ </p>
+ <p>
+ <br />
+ <span style="text-align:left">
+ <?=gettext("Password:"); ?> <br />
+ <input onclick="clearError();" onchange="clearError();" id="passwordfld" type="password" name="passwordfld" class="formfld pwd" tabindex="2" />
+ </span>
+ </p>
+ <p>
+ <br />
+ <span style="text-align:center; font-weight: normal ; font-style: italic">
+ <?=gettext("Enter username and password to login."); ?>
+ </span>
+
+ <span style="text-align:center; font-weight: normal ; font-style: italic; color: #ff0000; display:none" id="no_cookies">
+ <br /><br />
+ <?= gettext("Your browser must support cookies to login."); ?>
+ </span>
+ </p>
+ <p>
+ <span style="text-align:center">
+ <input type="submit" name="login" class="formbtn" value="<?=gettext("Login"); ?>" tabindex="3" />
+ </span>
+ </p>
+ </form>
+ </div>
+ <script type="text/javascript">
+ //<![CDATA[
+ document.cookie=
+ "cookie_test=1" +
+ "<?php echo $config['system']['webgui']['protocol'] == 'https' ? '; secure' : '';?>";
+
+ if (document.cookie.indexOf("cookie_test") == -1) {
+ document.getElementById("no_cookies").style.display="";
+ }
+
+ // Delete it
+ document.cookie = "cookie_test=1; expires=Thu, 01-Jan-1970 00:00:01 GMT";
+ //]]>
+ </script>
+ </body>
+</html>
+<?php
+} // end function
+
+?>
diff --git a/src/etc/inc/basic_sasl_client.inc b/src/etc/inc/basic_sasl_client.inc
new file mode 100644
index 0000000..c817664
--- /dev/null
+++ b/src/etc/inc/basic_sasl_client.inc
@@ -0,0 +1,63 @@
+<?php
+/*
+ * basic_sasl_client.php
+ *
+ * @(#) $Id: basic_sasl_client.php,v 1.1 2004/11/17 08:01:23 mlemos Exp $
+ *
+ */
+
+define("SASL_BASIC_STATE_START", 0);
+define("SASL_BASIC_STATE_DONE", 1);
+
+class basic_sasl_client_class
+{
+ var $credentials=array();
+ var $state=SASL_BASIC_STATE_START;
+
+ Function Initialize(&$client)
+ {
+ return(1);
+ }
+
+ Function Start(&$client, &$message, &$interactions)
+ {
+ if ($this->state!=SASL_BASIC_STATE_START)
+ {
+ $client->error="Basic authentication state is not at the start";
+ return(SASL_FAIL);
+ }
+ $this->credentials=array(
+ "user"=>"",
+ "password"=>""
+ );
+ $defaults=array(
+ );
+ $status=$client->GetCredentials($this->credentials,$defaults,$interactions);
+ if ($status==SASL_CONTINUE)
+ {
+ $message=$this->credentials["user"].":".$this->credentials["password"];
+ $this->state=SASL_BASIC_STATE_DONE;
+ }
+ else
+ {
+ Unset($message);
+ }
+ return($status);
+ }
+
+ Function Step(&$client, $response, &$message, &$interactions)
+ {
+ switch ($this->state)
+ {
+ case SASL_BASIC_STATE_DONE:
+ $client->error="Basic authentication was finished without success";
+ return(SASL_FAIL);
+ default:
+ $client->error="invalid Basic authentication step state";
+ return(SASL_FAIL);
+ }
+ return(SASL_CONTINUE);
+ }
+};
+
+?> \ No newline at end of file
diff --git a/src/etc/inc/captiveportal.inc b/src/etc/inc/captiveportal.inc
new file mode 100644
index 0000000..bd294e4
--- /dev/null
+++ b/src/etc/inc/captiveportal.inc
@@ -0,0 +1,2409 @@
+<?php
+/*
+ captiveportal.inc
+ part of pfSense (https://www.pfsense.org)
+ Copyright (C) 2004-2011 Scott Ullrich <sullrich@gmail.com>
+ Copyright (C) 2009-2012 Ermal Luçi <eri@pfsense.org>
+ Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>.
+
+ originally part of m0n0wall (http://m0n0.ch/wall)
+ 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.
+
+ This version of captiveportal.inc has been modified by Rob Parker
+ <rob.parker@keycom.co.uk> to include changes for per-user bandwidth management
+ via returned RADIUS attributes. This page has been modified to delete any
+ added rules which may have been created by other per-user code (index.php, etc).
+ These changes are (c) 2004 Keycom PLC.
+
+ pfSense_BUILDER_BINARIES: /sbin/ipfw /sbin/route
+ pfSense_BUILDER_BINARIES: /usr/local/sbin/lighttpd /usr/local/bin/minicron /sbin/pfctl
+ pfSense_BUILDER_BINARIES: /bin/hostname /bin/cp
+ pfSense_MODULE: captiveportal
+*/
+
+/* include all configuration functions */
+require_once("config.inc");
+require_once("functions.inc");
+require_once("filter.inc");
+require_once("radius.inc");
+require_once("voucher.inc");
+
+function get_default_captive_portal_html() {
+ global $config, $g, $cpzone;
+
+ $htmltext = <<<EOD
+<html>
+<body>
+<form method="post" action="\$PORTAL_ACTION\$">
+ <input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
+ <input name="zone" type="hidden" value="\$PORTAL_ZONE\$">
+ <center>
+ <table cellpadding="6" cellspacing="0" width="550" height="380" style="border:1px solid #000000">
+ <tr height="10" bgcolor="#990000">
+ <td style="border-bottom:1px solid #000000">
+ <font color='white'>
+ <b>
+ {$g['product_name']} captive portal
+ </b>
+ </font>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <div id="mainlevel">
+ <center>
+ <table width="100%" border="0" cellpadding="5" cellspacing="0">
+ <tr>
+ <td>
+ <center>
+ <div id="mainarea">
+ <center>
+ <table width="100%" border="0" cellpadding="5" cellspacing="5">
+ <tr>
+ <td>
+ <div id="maindivarea">
+ <center>
+ <div id='statusbox'>
+ <font color='red' face='arial' size='+1'>
+ <b>
+ \$PORTAL_MESSAGE\$
+ </b>
+ </font>
+ </div>
+ <br />
+ <div id='loginbox'>
+ <table>
+ <tr><td colspan="2"><center>Welcome to the {$g['product_name']} Captive Portal!</td></tr>
+ <tr><td>&nbsp;</td></tr>
+ <tr><td align="right">Username:</td><td><input name="auth_user" type="text" style="border: 1px dashed;"></td></tr>
+ <tr><td align="right">Password:</td><td><input name="auth_pass" type="password" style="border: 1px dashed;"></td></tr>
+ <tr><td>&nbsp;</td></tr>
+
+EOD;
+
+ if (isset($config['voucher'][$cpzone]['enable'])) {
+ $htmltext .= <<<EOD
+ <tr>
+ <td align="right">Enter Voucher Code: </td>
+ <td><input name="auth_voucher" type="text" style="border:1px dashed;" size="22"></td>
+ </tr>
+
+EOD;
+ }
+
+ $htmltext .= <<<EOD
+ <tr>
+ <td colspan="2"><center><input name="accept" type="submit" value="Continue"></center></td>
+ </tr>
+ </table>
+ </div>
+ </center>
+ </div>
+ </td>
+ </tr>
+ </table>
+ </center>
+ </div>
+ </center>
+ </td>
+ </tr>
+ </table>
+ </center>
+ </div>
+ </td>
+ </tr>
+ </table>
+ </center>
+</form>
+</body>
+</html>
+
+EOD;
+
+ return $htmltext;
+}
+
+function captiveportal_load_modules() {
+ global $config;
+
+ mute_kernel_msgs();
+ if (!is_module_loaded("ipfw.ko")) {
+ mwexec("/sbin/kldload ipfw");
+ /* make sure ipfw is not on pfil hooks */
+ set_sysctl(array(
+ "net.inet.ip.pfil.inbound" => "pf", "net.inet6.ip6.pfil.inbound" => "pf",
+ "net.inet.ip.pfil.outbound" => "pf", "net.inet6.ip6.pfil.outbound" => "pf")
+ );
+ }
+ /* Activate layer2 filtering */
+ set_sysctl(array("net.link.ether.ipfw" => "1", "net.inet.ip.fw.one_pass" => "1"));
+
+ /* Always load dummynet now that even allowed ip and mac passthrough use it. */
+ if (!is_module_loaded("dummynet.ko")) {
+ mwexec("/sbin/kldload dummynet");
+ set_sysctl(array("net.inet.ip.dummynet.io_fast" => "1", "net.inet.ip.dummynet.hash_size" => "256"));
+ }
+ unmute_kernel_msgs();
+}
+
+function captiveportal_configure() {
+ global $config, $cpzone, $cpzoneid;
+
+ if (is_array($config['captiveportal'])) {
+ foreach ($config['captiveportal'] as $cpkey => $cp) {
+ $cpzone = $cpkey;
+ $cpzoneid = $cp['zoneid'];
+ captiveportal_configure_zone($cp);
+ }
+ }
+}
+
+function captiveportal_configure_zone($cpcfg) {
+ global $config, $g, $cpzone, $cpzoneid;
+
+ $captiveportallck = lock("captiveportal{$cpzone}", LOCK_EX);
+
+ if (isset($cpcfg['enable'])) {
+
+ if (platform_booting()) {
+ echo "Starting captive portal({$cpcfg['zone']})... ";
+
+ /* remove old information */
+ unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
+ } else {
+ captiveportal_syslog("Reconfiguring captive portal({$cpcfg['zone']}).");
+ }
+
+ /* init ipfw rules */
+ captiveportal_init_rules(true);
+
+ /* kill any running minicron */
+ killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
+
+ /* initialize minicron interval value */
+ $croninterval = $cpcfg['croninterval'] ? $cpcfg['croninterval'] : 60;
+
+ /* double check if the $croninterval is numeric and at least 10 seconds. If not we set it to 60 to avoid problems */
+ if ((!is_numeric($croninterval)) || ($croninterval < 10)) {
+ $croninterval = 60;
+ }
+
+ /* write portal page */
+ if (is_array($cpcfg['page']) && $cpcfg['page']['htmltext']) {
+ $htmltext = base64_decode($cpcfg['page']['htmltext']);
+ } else {
+ /* example/template page */
+ $htmltext = get_default_captive_portal_html();
+ }
+
+ $fd = @fopen("{$g['varetc_path']}/captiveportal_{$cpzone}.html", "w");
+ if ($fd) {
+ // Special case handling. Convert so that we can pass this page
+ // through the PHP interpreter later without clobbering the vars.
+ $htmltext = str_replace("\$PORTAL_ZONE\$", "#PORTAL_ZONE#", $htmltext);
+ $htmltext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $htmltext);
+ $htmltext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $htmltext);
+ $htmltext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $htmltext);
+ $htmltext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $htmltext);
+ $htmltext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $htmltext);
+ $htmltext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $htmltext);
+ if ($cpcfg['preauthurl']) {
+ $htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
+ $htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
+ }
+ fwrite($fd, $htmltext);
+ fclose($fd);
+ }
+ unset($htmltext);
+
+ /* write error page */
+ if (is_array($cpcfg['page']) && $cpcfg['page']['errtext']) {
+ $errtext = base64_decode($cpcfg['page']['errtext']);
+ } else {
+ /* example page */
+ $errtext = get_default_captive_portal_html();
+ }
+
+ $fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html", "w");
+ if ($fd) {
+ // Special case handling. Convert so that we can pass this page
+ // through the PHP interpreter later without clobbering the vars.
+ $errtext = str_replace("\$PORTAL_ZONE\$", "#PORTAL_ZONE#", $errtext);
+ $errtext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $errtext);
+ $errtext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $errtext);
+ $errtext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $errtext);
+ $errtext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $errtext);
+ $errtext = str_replace("\$ORIGINAL_PORTAL_IP\$", "#ORIGINAL_PORTAL_IP#", $errtext);
+ $errtext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $errtext);
+ if ($cpcfg['preauthurl']) {
+ $errtext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $errtext);
+ $errtext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $errtext);
+ }
+ fwrite($fd, $errtext);
+ fclose($fd);
+ }
+ unset($errtext);
+
+ /* write logout page */
+ if (is_array($cpcfg['page']) && $cpcfg['page']['logouttext']) {
+ $logouttext = base64_decode($cpcfg['page']['logouttext']);
+ } else {
+ /* example page */
+ $logouttext = <<<EOD
+<html>
+<head><title>Redirecting...</title></head>
+<body>
+<span style="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
+<b>Redirecting to <a href="<?=\$my_redirurl;?>"><?=\$my_redirurl;?></a>...</b>
+</span>
+<script type="text/javascript">
+//<![CDATA[
+LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64');
+if (LogoutWin) {
+ LogoutWin.document.write('<html>');
+ LogoutWin.document.write('<head><title>Logout</title></head>') ;
+ LogoutWin.document.write('<body bgcolor="#435370">');
+ LogoutWin.document.write('<div align="center" style="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">') ;
+ LogoutWin.document.write('<b>Click the button below to disconnect</b><p />');
+ LogoutWin.document.write('<form method="POST" action="<?=\$logouturl;?>">');
+ LogoutWin.document.write('<input name="logout_id" type="hidden" value="<?=\$sessionid;?>" />');
+ LogoutWin.document.write('<input name="zone" type="hidden" value="<?=\$cpzone;?>" />');
+ LogoutWin.document.write('<input name="logout" type="submit" value="Logout" />');
+ LogoutWin.document.write('</form>');
+ LogoutWin.document.write('</div></body>');
+ LogoutWin.document.write('</html>');
+ LogoutWin.document.close();
+}
+
+document.location.href="<?=\$my_redirurl;?>";
+//]]>
+</script>
+</body>
+</html>
+
+EOD;
+ }
+
+ $fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html", "w");
+ if ($fd) {
+ fwrite($fd, $logouttext);
+ fclose($fd);
+ }
+ unset($logouttext);
+
+ /* write elements */
+ captiveportal_write_elements();
+
+ /* kill any running mini_httpd */
+ killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
+ killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
+
+ /* start up the webserving daemon */
+ captiveportal_init_webgui_zone($cpcfg);
+
+ /* Kill any existing prunecaptiveportal processes */
+ if (file_exists("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid")) {
+ killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
+ }
+
+ /* start pruning process (interval defaults to 60 seconds) */
+ mwexec("/usr/local/bin/minicron $croninterval {$g['varrun_path']}/cp_prunedb_{$cpzone}.pid " .
+ "/etc/rc.prunecaptiveportal {$cpzone}");
+
+ /* generate radius server database */
+ unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
+ captiveportal_init_radius_servers();
+
+ if (platform_booting()) {
+ /* send Accounting-On to server */
+ captiveportal_send_server_accounting();
+ echo "done\n";
+ }
+
+ } else {
+ killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal.pid");
+ killbypid("{$g['varrun_path']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
+ killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
+ @unlink("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
+ @unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
+ @unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
+
+ captiveportal_radius_stop_all();
+
+ /* send Accounting-Off to server */
+ if (!platform_booting()) {
+ captiveportal_send_server_accounting(true);
+ }
+
+ /* remove old information */
+ unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
+ unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
+ unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
+ /* Release allocated pipes for this zone */
+ captiveportal_free_dnrules();
+
+ mwexec("/sbin/ipfw zone {$cpzoneid} destroy", true);
+
+ if (empty($config['captiveportal'])) {
+ set_single_sysctl("net.link.ether.ipfw", "0");
+ } else {
+ /* Deactivate ipfw(4) if not needed */
+ $cpactive = false;
+ if (is_array($config['captiveportal'])) {
+ foreach ($config['captiveportal'] as $cpkey => $cp) {
+ if (isset($cp['enable'])) {
+ $cpactive = true;
+ break;
+ }
+ }
+ }
+ if ($cpactive === false) {
+ set_single_sysctl("net.link.ether.ipfw", "0");
+ }
+ }
+ }
+
+ unlock($captiveportallck);
+
+ return 0;
+}
+
+function captiveportal_init_webgui() {
+ global $config, $cpzone;
+
+ if (is_array($config['captiveportal'])) {
+ foreach ($config['captiveportal'] as $cpkey => $cp) {
+ $cpzone = $cpkey;
+ captiveportal_init_webgui_zone($cp);
+ }
+ }
+}
+
+function captiveportal_init_webgui_zonename($zone) {
+ global $config, $cpzone;
+
+ if (isset($config['captiveportal'][$zone])) {
+ $cpzone = $zone;
+ captiveportal_init_webgui_zone($config['captiveportal'][$zone]);
+ }
+}
+
+function captiveportal_init_webgui_zone($cpcfg) {
+ global $g, $config, $cpzone;
+
+ if (!isset($cpcfg['enable'])) {
+ return;
+ }
+
+ if (isset($cpcfg['httpslogin'])) {
+ $cert = lookup_cert($cpcfg['certref']);
+ $crt = base64_decode($cert['crt']);
+ $key = base64_decode($cert['prv']);
+ $ca = ca_chain($cert);
+
+ /* generate lighttpd configuration */
+ if (!empty($cpcfg['listenporthttps'])) {
+ $listenporthttps = $cpcfg['listenporthttps'];
+ } else {
+ $listenporthttps = 8001 + $cpcfg['zoneid'];
+ }
+ system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf",
+ $crt, $key, $ca, "lighty-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal",
+ "cert-{$cpzone}-portal.pem", "ca-{$cpzone}-portal.pem", $cpzone);
+ }
+
+ /* generate lighttpd configuration */
+ if (!empty($cpcfg['listenporthttp'])) {
+ $listenporthttp = $cpcfg['listenporthttp'];
+ } else {
+ $listenporthttp = 8000 + $cpcfg['zoneid'];
+ }
+ system_generate_lighty_config("{$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf",
+ "", "", "", "lighty-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
+ "", "", $cpzone);
+
+ @unlink("{$g['varrun']}/lighty-{$cpzone}-CaptivePortal.pid");
+ /* attempt to start lighttpd */
+ $res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal.conf");
+
+ /* fire up https instance */
+ if (isset($cpcfg['httpslogin'])) {
+ @unlink("{$g['varrun']}/lighty-{$cpzone}-CaptivePortal-SSL.pid");
+ $res = mwexec("/usr/local/sbin/lighttpd -f {$g['varetc_path']}/lighty-{$cpzone}-CaptivePortal-SSL.conf");
+ }
+}
+
+function captiveportal_init_rules_byinterface($interface) {
+ global $cpzone, $cpzoneid, $config;
+
+ if (!is_array($config['captiveportal'])) {
+ return;
+ }
+
+ foreach ($config['captiveportal'] as $cpkey => $cp) {
+ $cpzone = $cpkey;
+ $cpzoneid = $cp['zoneid'];
+ $cpinterfaces = explode(",", $cp['interface']);
+ if (in_array($interface, $cpinterfaces)) {
+ captiveportal_init_rules();
+ break;
+ }
+ }
+}
+
+/* reinit will disconnect all users, be careful! */
+function captiveportal_init_rules($reinit = false) {
+ global $config, $g, $cpzone, $cpzoneid;
+
+ if (!isset($config['captiveportal'][$cpzone]['enable'])) {
+ return;
+ }
+
+ captiveportal_load_modules();
+ mwexec("/sbin/ipfw zone {$cpzoneid} create", true);
+
+ /* Cleanup so nothing is leaked */
+ captiveportal_free_dnrules();
+ unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
+
+ $cpips = array();
+ $ifaces = get_configured_interface_list();
+ $cpinterfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
+ $firsttime = 0;
+ foreach ($cpinterfaces as $cpifgrp) {
+ if (!isset($ifaces[$cpifgrp])) {
+ continue;
+ }
+ $tmpif = get_real_interface($cpifgrp);
+ if (!empty($tmpif)) {
+ $cpipm = get_interface_ip($cpifgrp);
+ if (is_ipaddr($cpipm)) {
+ $cpips[] = $cpipm;
+ if (!is_array($config['virtualip']) || !is_array($config['virtualip']['vip'])) {
+ continue;
+ }
+ foreach ($config['virtualip']['vip'] as $vip) {
+ if (($vip['interface'] == $cpifgrp) && (($vip['mode'] == "carp") || ($vip['mode'] == "ipalias"))) {
+ $cpips[] = $vip['subnet'];
+ }
+ }
+ }
+ mwexec("/sbin/ipfw zone {$cpzoneid} madd {$tmpif}", true);
+ }
+ }
+ if (count($cpips) > 0) {
+ $cpactive = true;
+ } else {
+ return false;
+ }
+
+ if ($reinit == false) {
+ $captiveportallck = lock("captiveportal{$cpzone}");
+ }
+
+ $cprules = <<<EOD
+
+flush
+add 65291 allow pfsync from any to any
+add 65292 allow carp from any to any
+
+# layer 2: pass ARP
+add 65301 pass layer2 mac-type arp,rarp
+# pfsense requires for WPA
+add 65302 pass layer2 mac-type 0x888e,0x88c7
+# PPP Over Ethernet Session Stage/Discovery Stage
+add 65303 pass layer2 mac-type 0x8863,0x8864
+
+# layer 2: block anything else non-IP(v4/v6)
+add 65307 deny layer2 not mac-type ip,ipv6
+
+EOD;
+
+ $rulenum = 65310;
+ /* These tables contain host ips */
+ $cprules .= "add {$rulenum} pass ip from any to table(100) in\n";
+ $rulenum++;
+ $cprules .= "add {$rulenum} pass ip from table(100) to any out\n";
+ $rulenum++;
+ $ips = "";
+ foreach ($cpips as $cpip) {
+ $cprules .= "table 100 add {$cpip}\n";
+ }
+ $cprules .= "table 100 add 255.255.255.255\n";
+ $cprules .= "add {$rulenum} pass ip from any to {$ips} in\n";
+ $rulenum++;
+ $cprules .= "add {$rulenum} pass ip from {$ips} to any out\n";
+ $rulenum++;
+ $cprules .= "add {$rulenum} pass icmp from {$ips} to any out icmptype 0\n";
+ $rulenum++;
+ $cprules .= "add {$rulenum} pass icmp from any to {$ips} in icmptype 8 \n";
+ $rulenum++;
+ /* Allowed ips */
+ $cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any in\n";
+ $rulenum++;
+ $cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) in\n";
+ $rulenum++;
+ $cprules .= "add {$rulenum} pipe tablearg ip from table(3) to any out\n";
+ $rulenum++;
+ $cprules .= "add {$rulenum} pipe tablearg ip from any to table(4) out\n";
+ $rulenum++;
+
+ /* Authenticated users rules. */
+ $cprules .= "add {$rulenum} pipe tablearg ip from table(1) to any in\n";
+ $rulenum++;
+ $cprules .= "add {$rulenum} pipe tablearg ip from any to table(2) out\n";
+ $rulenum++;
+
+ if (!empty($config['captiveportal'][$cpzone]['listenporthttp'])) {
+ $listenporthttp = $config['captiveportal'][$cpzone]['listenporthttp'];
+ } else {
+ $listenporthttp = 8000 + $cpzoneid;
+ }
+
+ if (isset($config['captiveportal'][$cpzone]['httpslogin'])) {
+ if (!empty($config['captiveportal'][$cpzone]['listenporthttps'])) {
+ $listenporthttps = $config['captiveportal'][$cpzone]['listenporthttps'];
+ } else {
+ $listenporthttps = 8001 + $cpzoneid;
+ }
+ if (!isset($config['captiveportal'][$cpzone]['nohttpsforwards'])) {
+ $cprules .= "add 65531 fwd 127.0.0.1,{$listenporthttps} tcp from any to any dst-port 443 in\n";
+ }
+ }
+
+ $cprules .= <<<EOD
+
+# redirect non-authenticated clients to captive portal
+add 65532 fwd 127.0.0.1,{$listenporthttp} tcp from any to any dst-port 80 in
+# let the responses from the captive portal web server back out
+add 65533 pass tcp from any to any out
+# block everything else
+add 65534 deny all from any to any
+
+EOD;
+
+ /* generate passthru mac database */
+ $cprules .= captiveportal_passthrumac_configure(true);
+ $cprules .= "\n";
+
+ /* allowed ipfw rules to make allowed ip work */
+ $cprules .= captiveportal_allowedip_configure();
+
+ /* allowed ipfw rules to make allowed hostnames work */
+ $cprules .= captiveportal_allowedhostname_configure();
+
+ /* load rules */
+ file_put_contents("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", $cprules);
+ mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/ipfw_{$cpzone}.cp.rules", true);
+ //@unlink("{$g['tmp_path']}/ipfw_{$cpzone}.cp.rules");
+ unset($cprules);
+
+ if ($reinit == false) {
+ unlock($captiveportallck);
+ }
+}
+
+/*
+ * Remove clients that have been around for longer than the specified amount of time
+ * db file structure:
+ * timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time,interim_interval
+ * (password is in Base64 and only saved when reauthentication is enabled)
+ */
+function captiveportal_prune_old() {
+ global $g, $config, $cpzone, $cpzoneid;
+
+ if (empty($cpzone)) {
+ return;
+ }
+
+ $cpcfg = $config['captiveportal'][$cpzone];
+ $vcpcfg = $config['voucher'][$cpzone];
+
+ /* check for expired entries */
+ $idletimeout = 0;
+ $timeout = 0;
+ if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout'])) {
+ $timeout = $cpcfg['timeout'] * 60;
+ }
+
+ if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout'])) {
+ $idletimeout = $cpcfg['idletimeout'] * 60;
+ }
+
+ /* Is there any job to do? */
+ if (!$timeout && !$idletimeout && !isset($cpcfg['reauthenticate']) &&
+ !isset($cpcfg['radiussession_timeout']) && !isset($vcpcfg['enable'])) {
+ return;
+ }
+
+ $radiussrvs = captiveportal_get_radius_servers();
+
+ /* Read database */
+ /* NOTE: while this can be simplified in non radius case keep as is for now */
+ $cpdb = captiveportal_read_db();
+
+ $unsetindexes = array();
+ $voucher_needs_sync = false;
+ /*
+ * Snapshot the time here to use for calculation to speed up the process.
+ * If something is missed next run will catch it!
+ */
+ $pruning_time = time();
+ $stop_time = $pruning_time;
+ foreach ($cpdb as $cpentry) {
+
+ $timedout = false;
+ $term_cause = 1;
+ if (empty($cpentry[11])) {
+ $cpentry[11] = 'first';
+ }
+ $radiusservers = $radiussrvs[$cpentry[11]];
+
+ /* hard timeout? */
+ if ($timeout) {
+ if (($pruning_time - $cpentry[0]) >= $timeout) {
+ $timedout = true;
+ $term_cause = 5; // Session-Timeout
+ }
+ }
+
+ /* Session-Terminate-Time */
+ if (!$timedout && !empty($cpentry[9])) {
+ if ($pruning_time >= $cpentry[9]) {
+ $timedout = true;
+ $term_cause = 5; // Session-Timeout
+ }
+ }
+
+ /* check if the radius idle_timeout attribute has been set and if its set change the idletimeout to this value */
+ $uidletimeout = (is_numeric($cpentry[8])) ? $cpentry[8] : $idletimeout;
+ /* if an idle timeout is specified, get last activity timestamp from ipfw */
+ if (!$timedout && $uidletimeout > 0) {
+ $lastact = captiveportal_get_last_activity($cpentry[2], $cpentry[3]);
+ /* If the user has logged on but not sent any traffic they will never be logged out.
+ * We "fix" this by setting lastact to the login timestamp.
+ */
+ $lastact = $lastact ? $lastact : $cpentry[0];
+ if ($lastact && (($pruning_time - $lastact) >= $uidletimeout)) {
+ $timedout = true;
+ $term_cause = 4; // Idle-Timeout
+ $stop_time = $lastact; // Entry added to comply with WISPr
+ }
+ }
+
+ /* if vouchers are configured, activate session timeouts */
+ if (!$timedout && isset($vcpcfg['enable']) && !empty($cpentry[7])) {
+ if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
+ $timedout = true;
+ $term_cause = 5; // Session-Timeout
+ $voucher_needs_sync = true;
+ }
+ }
+
+ /* if radius session_timeout is enabled and the session_timeout is not null, then check if the user should be logged out */
+ if (!$timedout && isset($cpcfg['radiussession_timeout']) && !empty($cpentry[7])) {
+ if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
+ $timedout = true;
+ $term_cause = 5; // Session-Timeout
+ }
+ }
+
+ if ($timedout) {
+ captiveportal_disconnect($cpentry, $radiusservers, $term_cause, $stop_time);
+ captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "TIMEOUT");
+ $unsetindexes[] = $cpentry[5];
+ }
+
+ /* do periodic RADIUS reauthentication? */
+ if (!$timedout && !empty($radiusservers)) {
+ if (isset($cpcfg['radacct_enable'])) {
+ if ($cpcfg['reauthenticateacct'] == "stopstart") {
+ /* stop and restart accounting */
+ RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
+ $cpentry[4], // username
+ $cpentry[5], // sessionid
+ $cpentry[0], // start time
+ $radiusservers,
+ $cpentry[2], // clientip
+ $cpentry[3], // clientmac
+ 10); // NAS Request
+ $clientsn = (is_ipaddrv6($cpentry[2])) ? 128 : 32;
+ $_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XZEROENTRY, 1, $cpentry[2], $clientsn, $cpentry[3]);
+ $_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XZEROENTRY, 2, $cpentry[2], $clientsn, $cpentry[3]);
+ RADIUS_ACCOUNTING_START($cpentry[1], // ruleno
+ $cpentry[4], // username
+ $cpentry[5], // sessionid
+ $radiusservers,
+ $cpentry[2], // clientip
+ $cpentry[3]); // clientmac
+ } else if ($cpcfg['reauthenticateacct'] == "interimupdate") {
+ $session_time = $pruning_time - $cpentry[0];
+ if (!empty($cpentry[10]) && $cpentry[10] > 60) {
+ $interval = $cpentry[10];
+ } else {
+ $interval = 0;
+ }
+ $past_interval_min = ($session_time > $interval);
+ if ($interval != 0) {
+ $within_interval = ($session_time % $interval >= 0 && $session_time % $interval <= 59);
+ }
+ if ($interval === 0 || ($interval > 0 && $past_interval_min && $within_interval)) {
+ RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
+ $cpentry[4], // username
+ $cpentry[5], // sessionid
+ $cpentry[0], // start time
+ $radiusservers,
+ $cpentry[2], // clientip
+ $cpentry[3], // clientmac
+ 10, // NAS Request
+ true); // Interim Updates
+ }
+ }
+ }
+
+ /* check this user against RADIUS again */
+ if (isset($cpcfg['reauthenticate'])) {
+ $auth_list = RADIUS_AUTHENTICATION($cpentry[4], // username
+ base64_decode($cpentry[6]), // password
+ $radiusservers,
+ $cpentry[2], // clientip
+ $cpentry[3], // clientmac
+ $cpentry[1]); // ruleno
+ if ($auth_list['auth_val'] == 3) {
+ captiveportal_disconnect($cpentry, $radiusservers, 17);
+ captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_DISCONNECT", $auth_list['reply_message']);
+ $unsetindexes[] = $cpentry[5];
+ } else if ($auth_list['auth_val'] == 2) {
+ captiveportal_reapply_attributes($cpentry, $auth_list);
+ }
+ }
+ }
+ }
+ unset($cpdb);
+
+ captiveportal_prune_old_automac();
+
+ if ($voucher_needs_sync == true) {
+ /* Trigger a sync of the vouchers on config */
+ send_event("service sync vouchers");
+ }
+
+ /* write database */
+ if (!empty($unsetindexes)) {
+ captiveportal_remove_entries($unsetindexes);
+ }
+}
+
+function captiveportal_prune_old_automac() {
+ global $g, $config, $cpzone, $cpzoneid;
+
+ if (is_array($config['captiveportal'][$cpzone]['passthrumac']) && isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
+ $tmpvoucherdb = array();
+ $macrules = "";
+ $writecfg = false;
+ foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $eid => $emac) {
+ if ($emac['logintype'] == "voucher") {
+ if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
+ if (isset($tmpvoucherdb[$emac['username']])) {
+ $temac = $config['captiveportal'][$cpzone]['passthrumac'][$tmpvoucherdb[$emac['username']]];
+ $ruleno = captiveportal_get_ipfw_passthru_ruleno($temac['mac']);
+ $pipeno = captiveportal_get_dn_passthru_ruleno($temac['mac']);
+ if ($ruleno) {
+ captiveportal_free_ipfw_ruleno($ruleno);
+ $macrules .= "delete {$ruleno}";
+ ++$ruleno;
+ $macrules .= "delete {$ruleno}";
+ }
+ if ($pipeno) {
+ captiveportal_free_dn_ruleno($pipeno);
+ $macrules .= "pipe delete {$pipeno}\n";
+ ++$pipeno;
+ $macrules .= "pipe delete {$pipeno}\n";
+ }
+ $writecfg = true;
+ captiveportal_logportalauth($temac['username'], $temac['mac'], $temac['ip'], "DUPLICATE {$temac['username']} LOGIN - TERMINATING OLD SESSION");
+ unset($config['captiveportal'][$cpzone]['passthrumac'][$tmpvoucherdb[$emac['username']]]);
+ }
+ $tmpvoucherdb[$emac['username']] = $eid;
+ }
+ if (voucher_auth($emac['username']) <= 0) {
+ $ruleno = captiveportal_get_ipfw_passthru_ruleno($emac['mac']);
+ $pipeno = captiveportal_get_dn_passthru_ruleno($emac['mac']);
+ if ($ruleno) {
+ captiveportal_free_ipfw_ruleno($ruleno);
+ $macrules .= "delete {$ruleno}";
+ ++$ruleno;
+ $macrules .= "delete {$ruleno}";
+ }
+ if ($pipeno) {
+ captiveportal_free_dn_ruleno($pipeno);
+ $macrules .= "pipe delete {$pipeno}\n";
+ ++$pipeno;
+ $macrules .= "pipe delete {$pipeno}\n";
+ }
+ $writecfg = true;
+ captiveportal_logportalauth($emac['username'], $emac['mac'], $emac['ip'], "EXPIRED {$emac['username']} LOGIN - TERMINATING SESSION");
+ unset($config['captiveportal'][$cpzone]['passthrumac'][$eid]);
+ }
+ }
+ }
+ unset($tmpvoucherdb);
+ if (!empty($macrules)) {
+ @file_put_contents("{$g['tmp_path']}/macentry.prunerules.tmp", $macrules);
+ unset($macrules);
+ mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry.prunerules.tmp");
+ }
+ if ($writecfg === true) {
+ write_config("Prune session for auto-added macs");
+ }
+ }
+}
+
+/* remove a single client according to the DB entry */
+function captiveportal_disconnect($dbent, $radiusservers, $term_cause = 1, $stop_time = null) {
+ global $g, $config, $cpzone, $cpzoneid;
+
+ $stop_time = (empty($stop_time)) ? time() : $stop_time;
+
+ /* this client needs to be deleted - remove ipfw rules */
+ if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers)) {
+ RADIUS_ACCOUNTING_STOP($dbent[1], // ruleno
+ $dbent[4], // username
+ $dbent[5], // sessionid
+ $dbent[0], // start time
+ $radiusservers,
+ $dbent[2], // clientip
+ $dbent[3], // clientmac
+ $term_cause, // Acct-Terminate-Cause
+ false,
+ $stop_time);
+ }
+
+ if (is_ipaddr($dbent[2])) {
+ /* Delete client's ip entry from tables 1 and 2. */
+ $clientsn = (is_ipaddrv6($dbent[2])) ? 128 : 32;
+ pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XDEL, 1, $dbent[2], $clientsn, $dbent[3]);
+ pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XDEL, 2, $dbent[2], $clientsn, $dbent[3]);
+ /* XXX: Redundant?! Ensure all pf(4) states are killed. */
+ $_gb = @pfSense_kill_states($dbent[2]);
+ $_gb = @pfSense_kill_srcstates($dbent[2]);
+ }
+
+ /*
+ * These are the pipe numbers we use to control traffic shaping for each logged in user via captive portal
+ * We could get an error if the pipe doesn't exist but everything should still be fine
+ */
+ if (!empty($dbent[1])) {
+ $_gb = @pfSense_pipe_action("pipe delete {$dbent[1]}");
+ $_gb = @pfSense_pipe_action("pipe delete " . ($dbent[1]+1));
+
+ /* Release the ruleno so it can be reallocated to new clients. */
+ captiveportal_free_dn_ruleno($dbent[1]);
+ }
+
+ // XMLRPC Call over to the master Voucher node
+ if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
+ $syncip = $config['voucher'][$cpzone]['vouchersyncdbip'];
+ $syncport = $config['voucher'][$cpzone]['vouchersyncport'];
+ $syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
+ $vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
+ $remote_status = xmlrpc_sync_voucher_disconnect($dbent, $syncip, $syncport, $syncpass, $vouchersyncusername, $term_cause, $stop_time);
+ }
+
+}
+
+/* remove a single client by sessionid */
+function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
+ global $g, $config;
+
+ $radiusservers = captiveportal_get_radius_servers();
+
+ /* read database */
+ $result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
+
+ /* find entry */
+ if (!empty($result)) {
+ captiveportal_write_db("DELETE FROM captiveportal WHERE sessionid = '{$sessionid}'");
+
+ foreach ($result as $cpentry) {
+ if (empty($cpentry[11])) {
+ $cpentry[11] = 'first';
+ }
+ captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], $term_cause);
+ captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
+ }
+ unset($result);
+ }
+}
+
+/* send RADIUS acct stop for all current clients */
+function captiveportal_radius_stop_all() {
+ global $config, $cpzone;
+
+ if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
+ return;
+ }
+
+ $radiusservers = captiveportal_get_radius_servers();
+ if (!empty($radiusservers)) {
+ $cpdb = captiveportal_read_db();
+ foreach ($cpdb as $cpentry) {
+ if (empty($cpentry[11])) {
+ $cpentry[11] = 'first';
+ }
+ if (!empty($radiusservers[$cpentry[11]])) {
+ RADIUS_ACCOUNTING_STOP($cpentry[1], // ruleno
+ $cpentry[4], // username
+ $cpentry[5], // sessionid
+ $cpentry[0], // start time
+ $radiusservers[$cpentry[11]],
+ $cpentry[2], // clientip
+ $cpentry[3], // clientmac
+ 7); // Admin Reboot
+ }
+ }
+ }
+}
+
+function captiveportal_passthrumac_configure_entry($macent, $pipeinrule = false) {
+ global $config, $g, $cpzone;
+
+ $bwUp = 0;
+ if (!empty($macent['bw_up'])) {
+ $bwUp = $macent['bw_up'];
+ } else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup'])) {
+ $bwUp = $config['captiveportal'][$cpzone]['bwdefaultup'];
+ }
+ $bwDown = 0;
+ if (!empty($macent['bw_down'])) {
+ $bwDown = $macent['bw_down'];
+ } else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn'])) {
+ $bwDown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
+ }
+
+ $ruleno = captiveportal_get_next_ipfw_ruleno();
+
+ if ($macent['action'] == 'pass') {
+ $rules = "";
+ $pipeno = captiveportal_get_next_dn_ruleno();
+
+ $pipeup = $pipeno;
+ if ($pipeinrule == true) {
+ $_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16");
+ } else {
+ $rules .= "pipe {$pipeno} config bw {$bwUp}Kbit/s queue 100 buckets 16\n";
+ }
+
+ $pipedown = $pipeno + 1;
+ if ($pipeinrule == true) {
+ $_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
+ } else {
+ $rules .= "pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16\n";
+ }
+
+ $rules .= "add {$ruleno} pipe {$pipeup} ip from any to any MAC any {$macent['mac']}\n";
+ $ruleno++;
+ $rules .= "add {$ruleno} pipe {$pipedown} ip from any to any MAC {$macent['mac']} any\n";
+ }
+
+ return $rules;
+}
+
+function captiveportal_passthrumac_delete_entry($macent) {
+ $rules = "";
+
+ if ($macent['action'] == 'pass') {
+ $ruleno = captiveportal_get_ipfw_passthru_ruleno($macent['mac']);
+
+ if (!$ruleno) {
+ return $rules;
+ }
+
+ captiveportal_free_ipfw_ruleno($ruleno);
+
+ $rules .= "delete {$ruleno}\n";
+ $rules .= "delete " . ++$ruleno . "\n";
+
+ $pipeno = captiveportal_get_dn_passthru_ruleno($macent['mac']);
+
+ if (!empty($pipeno)) {
+ captiveportal_free_dn_ruleno($pipeno);
+ $rules .= "pipe delete " . $pipeno . "\n";
+ $rules .= "pipe delete " . ++$pipeno . "\n";
+ }
+ }
+
+ return $rules;
+}
+
+function captiveportal_passthrumac_configure($filename = false, $startindex = 0, $stopindex = 0) {
+ global $config, $g, $cpzone;
+
+ $rules = "";
+
+ if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
+ if ($stopindex > 0) {
+ $fd = fopen($filename, "w");
+ for ($idx = $startindex; $idx <= $stopindex; $idx++) {
+ if (isset($config['captiveportal'][$cpzone]['passthrumac'][$idx])) {
+ $rules = captiveportal_passthrumac_configure_entry($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
+ fwrite($fd, $rules);
+ }
+ }
+ fclose($fd);
+
+ return;
+ } else {
+ $nentries = count($config['captiveportal'][$cpzone]['passthrumac']);
+ if ($nentries > 2000) {
+ $nloops = $nentries / 1000;
+ $remainder= $nentries % 1000;
+ for ($i = 0; $i < $nloops; $i++) {
+ mwexec_bg("/usr/local/sbin/fcgicli -f /etc/rc.captiveportal_configure_mac -d \"cpzone={$cpzone}&startidx=" . ($i * 1000) . "&stopidx=" . ((($i+1) * 1000) - 1) . "\"");
+ }
+ if ($remainder > 0) {
+ mwexec_bg("/usr/local/sbin/fcgicli -f /etc/rc.captiveportal_configure_mac -d \"cpzone={$cpzone}&startidx=" . ($i * 1000) . "&stopidx=" . (($i* 1000) + $remainder) ."\"");
+ }
+ } else {
+ foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
+ $rules .= captiveportal_passthrumac_configure_entry($macent, true);
+ }
+ }
+ }
+ }
+
+ return $rules;
+}
+
+function captiveportal_passthrumac_findbyname($username) {
+ global $config, $cpzone;
+
+ if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
+ foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
+ if ($macent['username'] == $username) {
+ return $macent;
+ }
+ }
+ }
+ return NULL;
+}
+
+/*
+ * table (3=IN)/(4=OUT) hold allowed ip's without bw limits
+ */
+function captiveportal_allowedip_configure_entry($ipent, $ishostname = false) {
+ global $g;
+
+ /* Instead of copying this entire function for something
+ * easy such as hostname vs ip address add this check
+ */
+ if ($ishostname === true) {
+ if (!platform_booting()) {
+ $ipaddress = gethostbyname($ipent['hostname']);
+ if (!is_ipaddr($ipaddress)) {
+ return;
+ }
+ } else {
+ $ipaddress = "";
+ }
+ } else {
+ $ipaddress = $ipent['ip'];
+ }
+
+ $rules = "";
+ $cp_filterdns_conf = "";
+ $enBwup = 0;
+ if (!empty($ipent['bw_up'])) {
+ $enBwup = intval($ipent['bw_up']);
+ } else if (!empty($config['captiveportal'][$cpzone]['bwdefaultup'])) {
+ $enBwup = $config['captiveportal'][$cpzone]['bwdefaultup'];
+ }
+ $enBwdown = 0;
+ if (!empty($ipent['bw_down'])) {
+ $enBwdown = intval($ipent['bw_down']);
+ } else if (!empty($config['captiveportal'][$cpzone]['bwdefaultdn'])) {
+ $enBwdown = $config['captiveportal'][$cpzone]['bwdefaultdn'];
+ }
+
+ $pipeno = captiveportal_get_next_dn_ruleno();
+ $_gb = @pfSense_pipe_action("pipe {$pipeno} config bw {$enBwup}Kbit/s queue 100 buckets 16");
+ $pipedown = $pipeno + 1;
+ $_gb = @pfSense_pipe_action("pipe {$pipedown} config bw {$enBwdown}Kbit/s queue 100 buckets 16");
+ if ($ishostname === true) {
+ $cp_filterdns_conf .= "ipfw {$ipent['hostname']} 3 pipe {$pipeno}\n";
+ $cp_filterdns_conf .= "ipfw {$ipent['hostname']} 4 pipe {$pipedown}\n";
+ if (!is_ipaddr($ipaddress)) {
+ return array("", $cp_filterdns_conf);
+ }
+ }
+ $subnet = "";
+ if (!empty($ipent['sn'])) {
+ $subnet = "/{$ipent['sn']}";
+ }
+ $rules .= "table 3 add {$ipaddress}{$subnet} {$pipeno}\n";
+ $rules .= "table 4 add {$ipaddress}{$subnet} {$pipedown}\n";
+
+ if ($ishostname === true) {
+ return array($rules, $cp_filterdns_conf);
+ } else {
+ return $rules;
+ }
+}
+
+function captiveportal_allowedhostname_configure() {
+ global $config, $g, $cpzone, $cpzoneid;
+
+ $rules = "";
+ if (is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
+ $rules = "\n# captiveportal_allowedhostname_configure()\n";
+ $cp_filterdns_conf = "";
+ foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $hostnameent) {
+ $tmprules = captiveportal_allowedip_configure_entry($hostnameent, true);
+ $rules .= $tmprules[0];
+ $cp_filterdns_conf .= $tmprules[1];
+ }
+ $cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
+ @file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
+ unset($cp_filterdns_conf);
+ if (isvalidpid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid")) {
+ sigkillbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid", "HUP");
+ } else {
+ mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid -i 300 -c {$cp_filterdns_filename} -y {$cpzoneid} -d 1");
+ }
+ } else {
+ killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
+ @unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
+ }
+
+ return $rules;
+}
+
+function captiveportal_allowedip_configure() {
+ global $config, $g, $cpzone;
+
+ $rules = "";
+ if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
+ foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) {
+ $rules .= captiveportal_allowedip_configure_entry($ipent);
+ }
+ }
+
+ return $rules;
+}
+
+/* get last activity timestamp given client IP address */
+function captiveportal_get_last_activity($ip, $mac = NULL, $table = 1) {
+ global $cpzoneid;
+
+ $ipfwoutput = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, $table, $ip, $mac);
+ /* Reading only from one of the tables is enough of approximation. */
+ if (is_array($ipfwoutput)) {
+ /* Workaround for #46652 */
+ if ($ipfwoutput['packets'] > 0) {
+ return $ipfwoutput['timestamp'];
+ } else {
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+function captiveportal_init_radius_servers() {
+ global $config, $g, $cpzone;
+
+ /* generate radius server database */
+ if ($config['captiveportal'][$cpzone]['radiusip'] &&
+ (!isset($config['captiveportal'][$cpzone]['auth_method']) || $config['captiveportal'][$cpzone]['auth_method'] == "radius")) {
+ $radiusip = $config['captiveportal'][$cpzone]['radiusip'];
+ $radiusip2 = ($config['captiveportal'][$cpzone]['radiusip2']) ? $config['captiveportal'][$cpzone]['radiusip2'] : null;
+ $radiusip3 = ($config['captiveportal'][$cpzone]['radiusip3']) ? $config['captiveportal'][$cpzone]['radiusip3'] : null;
+ $radiusip4 = ($config['captiveportal'][$cpzone]['radiusip4']) ? $config['captiveportal'][$cpzone]['radiusip4'] : null;
+
+ if ($config['captiveportal'][$cpzone]['radiusport']) {
+ $radiusport = $config['captiveportal'][$cpzone]['radiusport'];
+ } else {
+ $radiusport = 1812;
+ }
+ if ($config['captiveportal'][$cpzone]['radiusacctport']) {
+ $radiusacctport = $config['captiveportal'][$cpzone]['radiusacctport'];
+ } else {
+ $radiusacctport = 1813;
+ }
+ if ($config['captiveportal'][$cpzone]['radiusport2']) {
+ $radiusport2 = $config['captiveportal'][$cpzone]['radiusport2'];
+ } else {
+ $radiusport2 = 1812;
+ }
+ if ($config['captiveportal'][$cpzone]['radiusport3']) {
+ $radiusport3 = $config['captiveportal'][$cpzone]['radiusport3'];
+ } else {
+ $radiusport3 = 1812;
+ }
+ if ($config['captiveportal'][$cpzone]['radiusport4']) {
+ $radiusport4 = $config['captiveportal'][$cpzone]['radiusport4'];
+ } else {
+ $radiusport4 = 1812;
+ }
+
+ $radiuskey = $config['captiveportal'][$cpzone]['radiuskey'];
+ $radiuskey2 = $config['captiveportal'][$cpzone]['radiuskey2'];
+ $radiuskey3 = $config['captiveportal'][$cpzone]['radiuskey3'];
+ $radiuskey4 = $config['captiveportal'][$cpzone]['radiuskey4'];
+
+ $cprdsrvlck = lock("captiveportalradius{$cpzone}", LOCK_EX);
+ $fd = @fopen("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db", "w");
+ if (!$fd) {
+ captiveportal_syslog("Error: cannot open radius DB file in captiveportal_configure().\n");
+ unlock($cprdsrvlck);
+ return 1;
+ }
+ if (isset($radiusip)) {
+ fwrite($fd, $radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey . ",first");
+ }
+ if (isset($radiusip2)) {
+ fwrite($fd, "\n" . $radiusip2 . "," . $radiusport2 . "," . $radiusacctport . "," . $radiuskey2 . ",first");
+ }
+ if (isset($radiusip3)) {
+ fwrite($fd, "\n" . $radiusip3 . "," . $radiusport3 . "," . $radiusacctport . "," . $radiuskey3 . ",second");
+ }
+ if (isset($radiusip4)) {
+ fwrite($fd, "\n" . $radiusip4 . "," . $radiusport4 . "," . $radiusacctport . "," . $radiuskey4 . ",second");
+ }
+
+ fclose($fd);
+ unlock($cprdsrvlck);
+ }
+}
+
+/* read RADIUS servers into array */
+function captiveportal_get_radius_servers() {
+ global $g, $cpzone;
+
+ $cprdsrvlck = lock("captiveportalradius{$cpzone}");
+ if (file_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db")) {
+ $radiusservers = array();
+ $cpradiusdb = file("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db",
+ FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+ if ($cpradiusdb) {
+ foreach ($cpradiusdb as $cpradiusentry) {
+ $line = trim($cpradiusentry);
+ if ($line) {
+ $radsrv = array();
+ list($radsrv['ipaddr'], $radsrv['port'], $radsrv['acctport'], $radsrv['key'], $context) = explode(",", $line);
+ }
+ if (empty($context)) {
+ if (!is_array($radiusservers['first'])) {
+ $radiusservers['first'] = array();
+ }
+ $radiusservers['first'] = $radsrv;
+ } else {
+ if (!is_array($radiusservers[$context])) {
+ $radiusservers[$context] = array();
+ }
+ $radiusservers[$context][] = $radsrv;
+ }
+ }
+ }
+ unlock($cprdsrvlck);
+ return $radiusservers;
+ }
+
+ unlock($cprdsrvlck);
+ return false;
+}
+
+/* log successful captive portal authentication to syslog */
+/* part of this code from php.net */
+function captiveportal_logportalauth($user, $mac, $ip, $status, $message = null) {
+ // Log it
+ if (!$message) {
+ $message = "{$status}: {$user}, {$mac}, {$ip}";
+ } else {
+ $message = trim($message);
+ $message = "{$status}: {$user}, {$mac}, {$ip}, {$message}";
+ }
+ captiveportal_syslog($message);
+}
+
+/* log simple messages to syslog */
+function captiveportal_syslog($message) {
+ global $cpzone;
+
+ $message = trim($message);
+ $message = "Zone: {$cpzone} - {$message}";
+ openlog("logportalauth", LOG_PID, LOG_LOCAL4);
+ // Log it
+ syslog(LOG_INFO, $message);
+ closelog();
+}
+
+function radius($username, $password, $clientip, $clientmac, $type, $radiusctx = null) {
+ global $g, $config, $cpzoneid;
+
+ $pipeno = captiveportal_get_next_dn_ruleno();
+
+ /* If the pool is empty, return appropriate message and fail authentication */
+ if (empty($pipeno)) {
+ $auth_list = array();
+ $auth_list['auth_val'] = 1;
+ $auth_list['error'] = "System reached maximum login capacity";
+ return $auth_list;
+ }
+
+ $radiusservers = captiveportal_get_radius_servers();
+
+ if (is_null($radiusctx)) {
+ $radiusctx = 'first';
+ }
+
+ $auth_list = RADIUS_AUTHENTICATION($username,
+ $password,
+ $radiusservers[$radiusctx],
+ $clientip,
+ $clientmac,
+ $pipeno);
+
+ if ($auth_list['auth_val'] == 2) {
+ captiveportal_logportalauth($username, $clientmac, $clientip, $type);
+ $sessionid = portal_allow($clientip,
+ $clientmac,
+ $username,
+ $password,
+ $auth_list,
+ $pipeno,
+ $radiusctx);
+ } else {
+ captiveportal_free_dn_ruleno($pipeno);
+ }
+
+ return $auth_list;
+}
+
+function captiveportal_opendb() {
+ global $g, $cpzone;
+
+ $db_path = "{$g['vardb_path']}/captiveportal{$cpzone}.db";
+ $createquery = "CREATE TABLE IF NOT EXISTS captiveportal (" .
+ "allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, " .
+ "sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, " .
+ "session_terminate_time INTEGER, interim_interval INTEGER, radiusctx TEXT); " .
+ "CREATE UNIQUE INDEX IF NOT EXISTS idx_active ON captiveportal (sessionid, username); " .
+ "CREATE INDEX IF NOT EXISTS user ON captiveportal (username); " .
+ "CREATE INDEX IF NOT EXISTS ip ON captiveportal (ip); " .
+ "CREATE INDEX IF NOT EXISTS starttime ON captiveportal (allow_time)";
+
+ try {
+ $DB = new SQLite3($db_path);
+ } catch (Exception $e) {
+ captiveportal_syslog("Could not open {$db_path} as an sqlite database for {$cpzone}. Error message: " . $e->getMessage() . " -- Trying again.");
+ unlink_if_exists($db_path);
+ try {
+ $DB = new SQLite3($db_path);
+ } catch (Exception $e) {
+ captiveportal_syslog("Still could not open {$db_path} as an sqlite database for {$cpzone}. Error message: " . $e->getMessage() . " -- Remove the database file manually and ensure there is enough free space.");
+ return;
+ }
+ }
+
+ if (!$DB) {
+ captiveportal_syslog("Could not open {$db_path} as an sqlite database for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Trying again.");
+ unlink_if_exists($db_path);
+ $DB = new SQLite3($db_path);
+ if (!$DB) {
+ captiveportal_syslog("Still could not open {$db_path} as an sqlite database for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Remove the database file manually and ensure there is enough free space.");
+ return;
+ }
+ }
+
+ if (! $DB->exec($createquery)) {
+ captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$DB->lastErrorMsg()}. Resetting and trying again.");
+
+ /* If unable to initialize the database, reset and try again. */
+ $DB->close();
+ unset($DB);
+ unlink_if_exists($db_path);
+ $DB = new SQLite3($db_path);
+ if ($DB->exec($createquery)) {
+ captiveportal_syslog("Successfully reinitialized tables for {$cpzone} -- database has been reset.");
+ } else {
+ captiveportal_syslog("Still unable to create tables for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Remove the database file manually and try again.");
+ }
+ }
+
+ return $DB;
+}
+
+/* read captive portal DB into array */
+function captiveportal_read_db($query = "") {
+ $cpdb = array();
+
+ $DB = captiveportal_opendb();
+ if ($DB) {
+ $response = $DB->query("SELECT * FROM captiveportal {$query}");
+ if ($response != FALSE) {
+ while ($row = $response->fetchArray()) {
+ $cpdb[] = $row;
+ }
+ }
+ $DB->close();
+ }
+
+ return $cpdb;
+}
+
+function captiveportal_remove_entries($remove) {
+
+ if (!is_array($remove) || empty($remove)) {
+ return;
+ }
+
+ $query = "DELETE FROM captiveportal WHERE sessionid in (";
+ foreach ($remove as $idx => $unindex) {
+ $query .= "'{$unindex}'";
+ if ($idx < (count($remove) - 1)) {
+ $query .= ",";
+ }
+ }
+ $query .= ")";
+ captiveportal_write_db($query);
+}
+
+/* write captive portal DB */
+function captiveportal_write_db($queries) {
+ global $g;
+
+ if (is_array($queries)) {
+ $query = implode(";", $queries);
+ } else {
+ $query = $queries;
+ }
+
+ $DB = captiveportal_opendb();
+ if ($DB) {
+ $DB->exec("BEGIN TRANSACTION");
+ $result = $DB->exec($query);
+ if (!$result) {
+ captiveportal_syslog("Trying to modify DB returned error: {$DB->lastErrorMsg()}");
+ } else {
+ $DB->exec("END TRANSACTION");
+ }
+ $DB->close();
+ return $result;
+ } else {
+ return true;
+ }
+}
+
+function captiveportal_write_elements() {
+ global $g, $config, $cpzone;
+
+ $cpcfg = $config['captiveportal'][$cpzone];
+
+ if (!is_dir($g['captiveportal_element_path'])) {
+ @mkdir($g['captiveportal_element_path']);
+ }
+
+ if (is_array($cpcfg['element'])) {
+ conf_mount_rw();
+ foreach ($cpcfg['element'] as $data) {
+ if (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
+ printf(gettext("Error: cannot open '%s' in captiveportal_write_elements()%s"), $data['name'], "\n");
+ return 1;
+ }
+ if (!file_exists("{$g['captiveportal_path']}/{$data['name']}")) {
+ @symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
+ }
+ }
+ conf_mount_ro();
+ }
+
+ return 0;
+}
+
+function captiveportal_free_dnrules($rulenos_start = 2000, $rulenos_range_max = 64500) {
+ global $cpzone;
+
+ $cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
+ if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
+ $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
+ $ridx = $rulenos_start;
+ while ($ridx < $rulenos_range_max) {
+ if ($rules[$ridx] == $cpzone) {
+ $rules[$ridx] = false;
+ $ridx++;
+ $rules[$ridx] = false;
+ $ridx++;
+ } else {
+ $ridx += 2;
+ }
+ }
+ file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
+ unset($rules);
+ }
+ unlock($cpruleslck);
+}
+
+function captiveportal_get_next_dn_ruleno($rulenos_start = 2000, $rulenos_range_max = 64500) {
+ global $config, $g, $cpzone;
+
+ $cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
+ $ruleno = 0;
+ if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
+ $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
+ $ridx = $rulenos_start;
+ while ($ridx < $rulenos_range_max) {
+ if (empty($rules[$ridx])) {
+ $ruleno = $ridx;
+ $rules[$ridx] = $cpzone;
+ $ridx++;
+ $rules[$ridx] = $cpzone;
+ break;
+ } else {
+ $ridx += 2;
+ }
+ }
+ } else {
+ $rules = array_pad(array(), $rulenos_range_max, false);
+ $ruleno = $rulenos_start;
+ $rules[$rulenos_start] = $cpzone;
+ $rulenos_start++;
+ $rules[$rulenos_start] = $cpzone;
+ }
+ file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
+ unlock($cpruleslck);
+ unset($rules);
+
+ return $ruleno;
+}
+
+function captiveportal_free_dn_ruleno($ruleno) {
+ global $config, $g;
+
+ $cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
+ if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
+ $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
+ $rules[$ruleno] = false;
+ $ruleno++;
+ $rules[$ruleno] = false;
+ file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
+ unset($rules);
+ }
+ unlock($cpruleslck);
+}
+
+function captiveportal_get_dn_passthru_ruleno($value) {
+ global $config, $g, $cpzone, $cpzoneid;
+
+ $cpcfg = $config['captiveportal'][$cpzone];
+ if (!isset($cpcfg['enable'])) {
+ return NULL;
+ }
+
+ $cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
+ $ruleno = NULL;
+ if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
+ $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
+ unset($output);
+ $_gb = exec("/sbin/ipfw -x {$cpzoneid} show | /usr/bin/grep " . escapeshellarg($value) . " | /usr/bin/grep -v grep | /usr/bin/awk '{print $5}' | /usr/bin/head -n 1", $output);
+ $ruleno = intval($output[0]);
+ if (!$rules[$ruleno]) {
+ $ruleno = NULL;
+ }
+ unset($rules);
+ }
+ unlock($cpruleslck);
+
+ return $ruleno;
+}
+
+/*
+ * This function will calculate the lowest free firewall ruleno
+ * within the range specified based on the actual logged on users
+ *
+ */
+function captiveportal_get_next_ipfw_ruleno($rulenos_start = 2, $rulenos_range_max = 64500) {
+ global $config, $g, $cpzone;
+
+ $cpcfg = $config['captiveportal'][$cpzone];
+ if (!isset($cpcfg['enable'])) {
+ return NULL;
+ }
+
+ $cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
+ $ruleno = 0;
+ if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
+ $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
+ $ridx = $rulenos_start;
+ while ($ridx < $rulenos_range_max) {
+ if (empty($rules[$ridx])) {
+ $ruleno = $ridx;
+ $rules[$ridx] = $cpzone;
+ $ridx++;
+ $rules[$ridx] = $cpzone;
+ break;
+ } else {
+ /*
+ * This allows our traffic shaping pipes to be the in pipe the same as ruleno
+ * and the out pipe ruleno + 1.
+ */
+ $ridx += 2;
+ }
+ }
+ } else {
+ $rules = array_pad(array(), $rulenos_range_max, false);
+ $ruleno = $rulenos_start;
+ $rules[$rulenos_start] = $cpzone;
+ $rulenos_start++;
+ $rules[$rulenos_start] = $cpzone;
+ }
+ file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
+ unlock($cpruleslck);
+ unset($rules);
+
+ return $ruleno;
+}
+
+function captiveportal_free_ipfw_ruleno($ruleno) {
+ global $config, $g, $cpzone;
+
+ $cpcfg = $config['captiveportal'][$cpzone];
+ if (!isset($cpcfg['enable'])) {
+ return NULL;
+ }
+
+ $cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
+ if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
+ $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
+ $rules[$ruleno] = false;
+ $ruleno++;
+ $rules[$ruleno] = false;
+ file_put_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules", serialize($rules));
+ unset($rules);
+ }
+ unlock($cpruleslck);
+}
+
+function captiveportal_get_ipfw_passthru_ruleno($value) {
+ global $config, $g, $cpzone, $cpzoneid;
+
+ $cpcfg = $config['captiveportal'][$cpzone];
+ if (!isset($cpcfg['enable'])) {
+ return NULL;
+ }
+
+ $cpruleslck = lock("captiveportalrules{$cpzone}", LOCK_EX);
+ $ruleno = NULL;
+ if (file_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules")) {
+ $rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportal_{$cpzone}.rules"));
+ unset($output);
+ $_gb = exec("/sbin/ipfw -x {$cpzoneid} show | /usr/bin/grep " . escapeshellarg($value) . " | /usr/bin/grep -v grep | /usr/bin/awk '{print $1}' | /usr/bin/head -n 1", $output);
+ $ruleno = intval($output[0]);
+ if (!$rules[$ruleno]) {
+ $ruleno = NULL;
+ }
+ unset($rules);
+ }
+ unlock($cpruleslck);
+
+ return $ruleno;
+}
+
+/**
+ * This function will calculate the traffic produced by a client
+ * based on its firewall rule
+ *
+ * Point of view: NAS
+ *
+ * Input means: from the client
+ * Output means: to the client
+ *
+ */
+
+function getVolume($ip, $mac = NULL) {
+ global $config, $cpzone, $cpzoneid;
+
+ $reverse = empty($config['captiveportal'][$cpzone]['reverseacct']) ? false : true;
+ $volume = array();
+ // Initialize vars properly, since we don't want NULL vars
+ $volume['input_pkts'] = $volume['input_bytes'] = $volume['output_pkts'] = $volume['output_bytes'] = 0 ;
+
+ $ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 1, $ip, $mac);
+ if (is_array($ipfw)) {
+ if ($reverse) {
+ $volume['output_pkts'] = $ipfw['packets'];
+ $volume['output_bytes'] = $ipfw['bytes'];
+ }
+ else {
+ $volume['input_pkts'] = $ipfw['packets'];
+ $volume['input_bytes'] = $ipfw['bytes'];
+ }
+ }
+
+ $ipfw = pfSense_ipfw_getTablestats($cpzoneid, IP_FW_TABLE_XLISTENTRY, 2, $ip, $mac);
+ if (is_array($ipfw)) {
+ if ($reverse) {
+ $volume['input_pkts'] = $ipfw['packets'];
+ $volume['input_bytes'] = $ipfw['bytes'];
+ }
+ else {
+ $volume['output_pkts'] = $ipfw['packets'];
+ $volume['output_bytes'] = $ipfw['bytes'];
+ }
+ }
+
+ return $volume;
+}
+
+/**
+ * Get the NAS-IP-Address based on the current wan address
+ *
+ * Use functions in interfaces.inc to find this out
+ *
+ */
+
+function getNasIP() {
+ global $config, $cpzone;
+
+ if (empty($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
+ $nasIp = get_interface_ip();
+ } else {
+ if (is_ipaddr($config['captiveportal'][$cpzone]['radiussrcip_attribute'])) {
+ $nasIp = $config['captiveportal'][$cpzone]['radiussrcip_attribute'];
+ } else {
+ $nasIp = get_interface_ip($config['captiveportal'][$cpzone]['radiussrcip_attribute']);
+ }
+ }
+
+ if (!is_ipaddr($nasIp)) {
+ $nasIp = "0.0.0.0";
+ }
+
+ return $nasIp;
+}
+
+function portal_ip_from_client_ip($cliip) {
+ global $config, $cpzone;
+
+ $isipv6 = is_ipaddrv6($cliip);
+ $interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
+ foreach ($interfaces as $cpif) {
+ if ($isipv6) {
+ $ip = get_interface_ipv6($cpif);
+ $sn = get_interface_subnetv6($cpif);
+ } else {
+ $ip = get_interface_ip($cpif);
+ $sn = get_interface_subnet($cpif);
+ }
+ if (ip_in_subnet($cliip, "{$ip}/{$sn}")) {
+ return $ip;
+ }
+ }
+
+ $inet = ($isipv6) ? '-inet6' : '-inet';
+ $iface = exec_command("/sbin/route -n get {$inet} {$cliip} | /usr/bin/awk '/interface/ { print \$2; };'");
+ $iface = trim($iface, "\n");
+ if (!empty($iface)) {
+ $ip = ($isipv6) ? find_interface_ipv6($iface) : find_interface_ip($iface);
+ if (is_ipaddr($ip)) {
+ return $ip;
+ }
+ }
+
+ // doesn't match up to any particular interface
+ // so let's set the portal IP to what PHP says
+ // the server IP issuing the request is.
+ // allows same behavior as 1.2.x where IP isn't
+ // in the subnet of any CP interface (static routes, etc.)
+ // rather than forcing to DNS hostname resolution
+ $ip = $_SERVER['SERVER_ADDR'];
+ if (is_ipaddr($ip)) {
+ return $ip;
+ }
+
+ return false;
+}
+
+function portal_hostname_from_client_ip($cliip) {
+ global $config, $cpzone;
+
+ $cpcfg = $config['captiveportal'][$cpzone];
+
+ if (isset($cpcfg['httpslogin'])) {
+ $listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 8001);
+ $ourhostname = $cpcfg['httpsname'];
+
+ if ($listenporthttps != 443) {
+ $ourhostname .= ":" . $listenporthttps;
+ }
+ } else {
+ $listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : ($cpcfg['zoneid'] + 8000);
+ $ifip = portal_ip_from_client_ip($cliip);
+ if (!$ifip) {
+ $ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
+ } else {
+ $ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
+ }
+
+ if ($listenporthttp != 80) {
+ $ourhostname .= ":" . $listenporthttp;
+ }
+ }
+
+ return $ourhostname;
+}
+
+/* functions move from index.php */
+
+function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null) {
+ global $g, $config, $cpzone;
+
+ /* Get captive portal layout */
+ if ($type == "redir") {
+ header("Location: {$redirurl}");
+ return;
+ } else if ($type == "login") {
+ $htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
+ } else {
+ $htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
+ }
+
+ $cpcfg = $config['captiveportal'][$cpzone];
+
+ /* substitute the PORTAL_REDIRURL variable */
+ if ($cpcfg['preauthurl']) {
+ $htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
+ $htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
+ }
+
+ /* substitute other variables */
+ $ourhostname = portal_hostname_from_client_ip($clientip);
+ $protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
+ $htmltext = str_replace("\$PORTAL_ACTION\$", "{$protocol}{$ourhostname}/", $htmltext);
+ $htmltext = str_replace("#PORTAL_ACTION#", "{$protocol}{$ourhostname}/", $htmltext);
+
+ $htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
+ $htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
+ $htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
+ $htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
+ $htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
+
+ // Special handling case for captive portal master page so that it can be ran
+ // through the PHP interpreter using the include method above. We convert the
+ // $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
+ $htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
+ $htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
+ $htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
+ $htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
+ $htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
+ $htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
+ $htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
+
+ echo $htmltext;
+}
+
+function portal_mac_radius($clientmac, $clientip) {
+ global $config, $cpzone;
+
+ $radmac_secret = $config['captiveportal'][$cpzone]['radmac_secret'];
+
+ /* authentication against the radius server */
+ $username = mac_format($clientmac);
+ $auth_list = radius($username, $radmac_secret, $clientip, $clientmac, "MACHINE LOGIN");
+ if ($auth_list['auth_val'] == 2) {
+ return TRUE;
+ }
+
+ if (!empty($auth_list['url_redirection'])) {
+ portal_reply_page($auth_list['url_redirection'], "redir");
+ }
+
+ return FALSE;
+}
+
+function captiveportal_reapply_attributes($cpentry, $attributes) {
+ global $config, $cpzone, $g;
+
+ if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
+ $dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
+ $dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
+ } else {
+ $dwfaultbw_up = $dwfaultbw_down = 0;
+ }
+ $bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
+ $bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
+ $bw_up_pipeno = $cpentry[1];
+ $bw_down_pipeno = $cpentry[1]+1;
+
+ $_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
+ $_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
+ //captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "RADIUS_BANDWIDTH_REAPPLY", "{$bw_up}/{$bw_down}");
+
+ unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
+}
+
+function portal_allow($clientip, $clientmac, $username, $password = null, $attributes = null, $pipeno = null, $radiusctx = null) {
+ global $redirurl, $g, $config, $type, $passthrumac, $_POST, $cpzone, $cpzoneid;
+
+ // Ensure we create an array if we are missing attributes
+ if (!is_array($attributes)) {
+ $attributes = array();
+ }
+
+ unset($sessionid);
+
+ /* Do not allow concurrent login execution. */
+ $cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
+
+ if ($attributes['voucher']) {
+ $remaining_time = $attributes['session_timeout'];
+ }
+
+ $writecfg = false;
+ /* Find an existing session */
+ if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && $passthrumac) {
+ if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
+ $mac = captiveportal_passthrumac_findbyname($username);
+ if (!empty($mac)) {
+ if ($_POST['replacemacpassthru']) {
+ foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
+ if ($macent['mac'] == $mac['mac']) {
+ $macrules = "";
+ $ruleno = captiveportal_get_ipfw_passthru_ruleno($mac['mac']);
+ $pipeno = captiveportal_get_dn_passthru_ruleno($mac['mac']);
+ if ($ruleno) {
+ captiveportal_free_ipfw_ruleno($ruleno);
+ $macrules .= "delete {$ruleno}\n";
+ ++$ruleno;
+ $macrules .= "delete {$ruleno}\n";
+ }
+ if ($pipeno) {
+ captiveportal_free_dn_ruleno($pipeno);
+ $macrules .= "pipe delete {$pipeno}\n";
+ ++$pipeno;
+ $macrules .= "pipe delete {$pipeno}\n";
+ }
+ unset($config['captiveportal'][$cpzone]['passthrumac'][$idx]);
+ $mac['action'] = 'pass';
+ $mac['mac'] = $clientmac;
+ $config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
+ $macrules .= captiveportal_passthrumac_configure_entry($mac);
+ file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
+ mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
+ $writecfg = true;
+ $sessionid = true;
+ break;
+ }
+ }
+ } else {
+ portal_reply_page($redirurl, "error", "Username: {$username} is already authenticated using another MAC address.",
+ $clientmac, $clientip, $username, $password);
+ unlock($cpdblck);
+ return;
+ }
+ }
+ }
+ }
+
+ /* read in client database */
+ $query = "WHERE ip = '{$clientip}'";
+ $tmpusername = strtolower($username);
+ if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
+ $query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
+ }
+ $cpdb = captiveportal_read_db($query);
+
+ /* Snapshot the timestamp */
+ $allow_time = time();
+ $radiusservers = captiveportal_get_radius_servers();
+ $unsetindexes = array();
+ if (is_null($radiusctx)) {
+ $radiusctx = 'first';
+ }
+
+ foreach ($cpdb as $cpentry) {
+ if (empty($cpentry[11])) {
+ $cpentry[11] = 'first';
+ }
+ /* on the same ip */
+ if ($cpentry[2] == $clientip) {
+ if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac) {
+ captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - REUSING OLD SESSION");
+ } else {
+ captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
+ }
+ $sessionid = $cpentry[5];
+ break;
+ } elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
+ // user logged in with an active voucher. Check for how long and calculate
+ // how much time we can give him (voucher credit - used time)
+ $remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
+ if ($remaining_time < 0) { // just in case.
+ $remaining_time = 0;
+ }
+
+ /* This user was already logged in so we disconnect the old one */
+ captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], 13);
+ captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - TERMINATING OLD SESSION");
+ $unsetindexes[] = $cpentry[5];
+ break;
+ } elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
+ /* on the same username */
+ if (strcasecmp($cpentry[4], $username) == 0) {
+ /* This user was already logged in so we disconnect the old one */
+ captiveportal_disconnect($cpentry, $radiusservers[$cpentry[11]], 13);
+ captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - TERMINATING OLD SESSION");
+ $unsetindexes[] = $cpentry[5];
+ break;
+ }
+ }
+ }
+ unset($cpdb);
+
+ if (!empty($unsetindexes)) {
+ captiveportal_remove_entries($unsetindexes);
+ }
+
+ if ($attributes['voucher'] && $remaining_time <= 0) {
+ return 0; // voucher already used and no time left
+ }
+
+ if (!isset($sessionid)) {
+ /* generate unique session ID */
+ $tod = gettimeofday();
+ $sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
+
+ if ($passthrumac) {
+ $mac = array();
+ $mac['action'] = 'pass';
+ $mac['mac'] = $clientmac;
+ $mac['ip'] = $clientip; /* Used only for logging */
+ if (isset($config['captiveportal'][$cpzone]['passthrumacaddusername'])) {
+ $mac['username'] = $username;
+ if ($attributes['voucher']) {
+ $mac['logintype'] = "voucher";
+ }
+ }
+ if ($username == "unauthenticated") {
+ $mac['descr'] = "Auto-added";
+ } else {
+ $mac['descr'] = "Auto-added for user {$username}";
+ }
+ if (!empty($bw_up)) {
+ $mac['bw_up'] = $bw_up;
+ }
+ if (!empty($bw_down)) {
+ $mac['bw_down'] = $bw_down;
+ }
+ if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
+ $config['captiveportal'][$cpzone]['passthrumac'] = array();
+ }
+ $config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
+ unlock($cpdblck);
+ $macrules = captiveportal_passthrumac_configure_entry($mac);
+ file_put_contents("{$g['tmp_path']}/macentry_{$cpzone}.rules.tmp", $macrules);
+ mwexec("/sbin/ipfw -x {$cpzoneid} -q {$g['tmp_path']}/macentry_{$cpzone}.rules.tmp");
+ $writecfg = true;
+ } else {
+ /* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
+ if (is_null($pipeno)) {
+ $pipeno = captiveportal_get_next_dn_ruleno();
+ }
+
+ /* if the pool is empty, return appropriate message and exit */
+ if (is_null($pipeno)) {
+ portal_reply_page($redirurl, "error", "System reached maximum login capacity");
+ log_error("Zone: {$cpzone} - WARNING! Captive portal has reached maximum login capacity");
+ unlock($cpdblck);
+ return;
+ }
+
+ if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
+ $dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
+ $dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
+ } else {
+ $dwfaultbw_up = $dwfaultbw_down = 0;
+ }
+ $bw_up = !empty($attributes['bw_up']) ? round(intval($attributes['bw_up'])/1000, 2) : $dwfaultbw_up;
+ $bw_down = !empty($attributes['bw_down']) ? round(intval($attributes['bw_down'])/1000, 2) : $dwfaultbw_down;
+
+ $bw_up_pipeno = $pipeno;
+ $bw_down_pipeno = $pipeno + 1;
+ //$bw_up /= 1000; // Scale to Kbit/s
+ $_gb = @pfSense_pipe_action("pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
+ $_gb = @pfSense_pipe_action("pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
+
+ $clientsn = (is_ipaddrv6($clientip)) ? 128 : 32;
+ if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
+ $_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, $clientmac, $bw_up_pipeno);
+ } else {
+ $_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 1, $clientip, $clientsn, NULL, $bw_up_pipeno);
+ }
+
+ if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
+ $_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, $clientmac, $bw_down_pipeno);
+ } else {
+ $_gb = @pfSense_ipfw_Tableaction($cpzoneid, IP_FW_TABLE_XADD, 2, $clientip, $clientsn, NULL, $bw_down_pipeno);
+ }
+
+ if ($attributes['voucher']) {
+ $attributes['session_timeout'] = $remaining_time;
+ }
+
+ /* handle empty attributes */
+ $session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
+ $idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
+ $session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
+ $interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
+
+ /* escape username */
+ $safe_username = SQLite3::escapeString($username);
+
+ /* encode password in Base64 just in case it contains commas */
+ $bpassword = base64_encode($password);
+ $insertquery = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, radiusctx) ";
+ $insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
+ $insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, '{$radiusctx}')";
+
+ /* store information to database */
+ captiveportal_write_db($insertquery);
+ unlock($cpdblck);
+ unset($insertquery, $bpassword);
+
+ if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && !empty($radiusservers[$radiusctx])) {
+ $acct_val = RADIUS_ACCOUNTING_START($pipeno, $username, $sessionid, $radiusservers[$radiusctx], $clientip, $clientmac);
+ if ($acct_val == 1) {
+ captiveportal_logportalauth($username, $clientmac, $clientip, $type, "RADIUS ACCOUNTING FAILED");
+ }
+ }
+ }
+ } else {
+ /* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP and maintain proper operation as in radius() case */
+ if (!is_null($pipeno)) {
+ captiveportal_free_dn_ruleno($pipeno);
+ }
+
+ unlock($cpdblck);
+ }
+
+ if ($writecfg == true) {
+ write_config();
+ }
+
+ /* redirect user to desired destination */
+ if (!empty($attributes['url_redirection'])) {
+ $my_redirurl = $attributes['url_redirection'];
+ } else if (!empty($redirurl)) {
+ $my_redirurl = $redirurl;
+ } else if (!empty($config['captiveportal'][$cpzone]['redirurl'])) {
+ $my_redirurl = $config['captiveportal'][$cpzone]['redirurl'];
+ }
+
+ if (isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !$passthrumac) {
+ $ourhostname = portal_hostname_from_client_ip($clientip);
+ $protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
+ $logouturl = "{$protocol}{$ourhostname}/";
+
+ if (isset($attributes['reply_message'])) {
+ $message = $attributes['reply_message'];
+ } else {
+ $message = 0;
+ }
+
+ include("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
+
+ } else {
+ portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
+ }
+
+ return $sessionid;
+}
+
+
+/*
+ * Used for when pass-through credits are enabled.
+ * Returns true when there was at least one free login to deduct for the MAC.
+ * Expired entries are removed as they are seen.
+ * Active entries are updated according to the configuration.
+ */
+function portal_consume_passthrough_credit($clientmac) {
+ global $config, $cpzone;
+
+ if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count'])) {
+ $freeloginscount = $config['captiveportal'][$cpzone]['freelogins_count'];
+ } else {
+ return false;
+ }
+
+ if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout'])) {
+ $resettimeout = $config['captiveportal'][$cpzone]['freelogins_resettimeout'];
+ } else {
+ return false;
+ }
+
+ if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac) {
+ return false;
+ }
+
+ $updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
+
+ /*
+ * Read database of used MACs. Lines are a comma-separated list
+ * of the time, MAC, then the count of pass-through credits remaining.
+ */
+ $usedmacs = captiveportal_read_usedmacs_db();
+
+ $currenttime = time();
+ $found = false;
+ foreach ($usedmacs as $key => $usedmac) {
+ $usedmac = explode(",", $usedmac);
+
+ if ($usedmac[1] == $clientmac) {
+ if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
+ if ($usedmac[2] < 1) {
+ if ($updatetimeouts) {
+ $usedmac[0] = $currenttime;
+ unset($usedmacs[$key]);
+ $usedmacs[] = implode(",", $usedmac);
+ captiveportal_write_usedmacs_db($usedmacs);
+ }
+
+ return false;
+ } else {
+ $usedmac[2] -= 1;
+ $usedmacs[$key] = implode(",", $usedmac);
+ }
+
+ $found = true;
+ } else {
+ unset($usedmacs[$key]);
+ }
+
+ break;
+ } else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime) {
+ unset($usedmacs[$key]);
+ }
+ }
+
+ if (!$found) {
+ $usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
+ $usedmacs[] = implode(",", $usedmac);
+ }
+
+ captiveportal_write_usedmacs_db($usedmacs);
+ return true;
+}
+
+function captiveportal_read_usedmacs_db() {
+ global $g, $cpzone;
+
+ $cpumaclck = lock("captiveusedmacs{$cpzone}");
+ if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
+ $usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+ if (!$usedmacs) {
+ $usedmacs = array();
+ }
+ } else {
+ $usedmacs = array();
+ }
+
+ unlock($cpumaclck);
+ return $usedmacs;
+}
+
+function captiveportal_write_usedmacs_db($usedmacs) {
+ global $g, $cpzone;
+
+ $cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
+ @file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
+ unlock($cpumaclck);
+}
+
+function captiveportal_blocked_mac($mac) {
+ global $config, $g, $cpzone;
+
+ if (empty($mac) || !is_macaddr($mac)) {
+ return false;
+ }
+
+ if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
+ return false;
+ }
+
+ foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac) {
+ if (($passthrumac['action'] == 'block') &&
+ ($passthrumac['mac'] == strtolower($mac))) {
+ return true;
+ }
+ }
+
+ return false;
+
+}
+
+function captiveportal_send_server_accounting($off = false) {
+ global $cpzone, $config;
+
+ if (!isset($config['captiveportal'][$cpzone]['radacct_enable'])) {
+ return;
+ }
+ if ($off) {
+ $racct = new Auth_RADIUS_Acct_Off;
+ } else {
+ $racct = new Auth_RADIUS_Acct_On;
+ }
+ $radiusservers = captiveportal_get_radius_servers();
+ if (empty($radiusservers)) {
+ return;
+ }
+ foreach ($radiusservers['first'] as $radsrv) {
+ // Add a new server to our instance
+ $racct->addServer($radsrv['ipaddr'], $radsrv['acctport'], $radsrv['key']);
+ }
+ if (PEAR::isError($racct->start())) {
+ $retvalue['acct_val'] = 1;
+ $retvalue['error'] = $racct->getMessage();
+
+ // If we encounter an error immediately stop this function and go back
+ $racct->close();
+ return $retvalue;
+ }
+ // Send request
+ $result = $racct->send();
+ // Evaluation of the response
+ // 5 -> Accounting-Response
+ // See RFC2866 for this.
+ if (PEAR::isError($result)) {
+ $retvalue['acct_val'] = 1;
+ $retvalue['error'] = $result->getMessage();
+ } else if ($result === true) {
+ $retvalue['acct_val'] = 5 ;
+ } else {
+ $retvalue['acct_val'] = 1 ;
+ }
+
+ $racct->close();
+ return $retvalue;
+}
+?>
diff --git a/src/etc/inc/certs.inc b/src/etc/inc/certs.inc
new file mode 100644
index 0000000..9c99952
--- /dev/null
+++ b/src/etc/inc/certs.inc
@@ -0,0 +1,867 @@
+<?php
+/* $Id$ */
+/*
+ certs.inc
+ Copyright (C) 2008 Shrew Soft Inc
+ Copyright (C) 2010 Jim Pingle <jimp@pfsense.org>
+ 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.
+
+ pfSense_MODULE: certificate_manager
+*/
+
+define("OPEN_SSL_CONF_PATH", "/etc/ssl/openssl.cnf");
+
+require_once("functions.inc");
+
+global $openssl_digest_algs;
+$openssl_digest_algs = array("sha1", "sha224", "sha256", "sha384", "sha512");
+
+global $openssl_crl_status;
+$openssl_crl_status = array(
+ OCSP_REVOKED_STATUS_NOSTATUS => "No Status (default)",
+ OCSP_REVOKED_STATUS_UNSPECIFIED => "Unspecified",
+ OCSP_REVOKED_STATUS_KEYCOMPROMISE => "Key Compromise",
+ OCSP_REVOKED_STATUS_CACOMPROMISE => "CA Compromise",
+ OCSP_REVOKED_STATUS_AFFILIATIONCHANGED => "Affiliation Changed",
+ OCSP_REVOKED_STATUS_SUPERSEDED => "Superseded",
+ OCSP_REVOKED_STATUS_CESSATIONOFOPERATION => "Cessation of Operation",
+ OCSP_REVOKED_STATUS_CERTIFICATEHOLD => "Certificate Hold"
+);
+
+function & lookup_ca($refid) {
+ global $config;
+
+ if (is_array($config['ca'])) {
+ foreach ($config['ca'] as & $ca) {
+ if ($ca['refid'] == $refid) {
+ return $ca;
+ }
+ }
+ }
+
+ return false;
+}
+
+function & lookup_ca_by_subject($subject) {
+ global $config;
+
+ if (is_array($config['ca'])) {
+ foreach ($config['ca'] as & $ca) {
+ $ca_subject = cert_get_subject($ca['crt']);
+ if ($ca_subject == $subject) {
+ return $ca;
+ }
+ }
+ }
+
+ return false;
+}
+
+function & lookup_cert($refid) {
+ global $config;
+
+ if (is_array($config['cert'])) {
+ foreach ($config['cert'] as & $cert) {
+ if ($cert['refid'] == $refid) {
+ return $cert;
+ }
+ }
+ }
+
+ return false;
+}
+
+function & lookup_cert_by_name($name) {
+ global $config;
+ if (is_array($config['cert'])) {
+ foreach ($config['cert'] as & $cert) {
+ if ($cert['descr'] == $name) {
+ return $cert;
+ }
+ }
+ }
+}
+
+function & lookup_crl($refid) {
+ global $config;
+
+ if (is_array($config['crl'])) {
+ foreach ($config['crl'] as & $crl) {
+ if ($crl['refid'] == $refid) {
+ return $crl;
+ }
+ }
+ }
+
+ return false;
+}
+
+function ca_chain_array(& $cert) {
+ if ($cert['caref']) {
+ $chain = array();
+ $crt = lookup_ca($cert['caref']);
+ $chain[] = $crt;
+ while ($crt) {
+ $caref = $crt['caref'];
+ if ($caref) {
+ $crt = lookup_ca($caref);
+ } else {
+ $crt = false;
+ }
+ if ($crt) {
+ $chain[] = $crt;
+ }
+ }
+ return $chain;
+ }
+ return false;
+}
+
+function ca_chain(& $cert) {
+ if ($cert['caref']) {
+ $ca = "";
+ $cas = ca_chain_array($cert);
+ if (is_array($cas)) {
+ foreach ($cas as & $ca_cert) {
+ $ca .= base64_decode($ca_cert['crt']);
+ $ca .= "\n";
+ }
+ }
+ return $ca;
+ }
+ return "";
+}
+
+function ca_import(& $ca, $str, $key = "", $serial = 0) {
+ global $config;
+
+ $ca['crt'] = base64_encode($str);
+ if (!empty($key)) {
+ $ca['prv'] = base64_encode($key);
+ }
+ if (!empty($serial)) {
+ $ca['serial'] = $serial;
+ }
+ $subject = cert_get_subject($str, false);
+ $issuer = cert_get_issuer($str, false);
+
+ // Find my issuer unless self-signed
+ if ($issuer <> $subject) {
+ $issuer_crt =& lookup_ca_by_subject($issuer);
+ if ($issuer_crt) {
+ $ca['caref'] = $issuer_crt['refid'];
+ }
+ }
+
+ /* Correct if child certificate was loaded first */
+ if (is_array($config['ca'])) {
+ foreach ($config['ca'] as & $oca) {
+ $issuer = cert_get_issuer($oca['crt']);
+ if ($ca['refid'] <> $oca['refid'] && $issuer == $subject) {
+ $oca['caref'] = $ca['refid'];
+ }
+ }
+ }
+ if (is_array($config['cert'])) {
+ foreach ($config['cert'] as & $cert) {
+ $issuer = cert_get_issuer($cert['crt']);
+ if ($issuer == $subject) {
+ $cert['caref'] = $ca['refid'];
+ }
+ }
+ }
+ return true;
+}
+
+function ca_create(& $ca, $keylen, $lifetime, $dn, $digest_alg = "sha256") {
+
+ $args = array(
+ "x509_extensions" => "v3_ca",
+ "digest_alg" => $digest_alg,
+ "private_key_bits" => (int)$keylen,
+ "private_key_type" => OPENSSL_KEYTYPE_RSA,
+ "encrypt_key" => false);
+
+ // generate a new key pair
+ $res_key = openssl_pkey_new($args);
+ if (!$res_key) {
+ return false;
+ }
+
+ // generate a certificate signing request
+ $res_csr = openssl_csr_new($dn, $res_key, $args);
+ if (!$res_csr) {
+ return false;
+ }
+
+ // self sign the certificate
+ $res_crt = openssl_csr_sign($res_csr, null, $res_key, $lifetime, $args);
+ if (!$res_crt) {
+ return false;
+ }
+
+ // export our certificate data
+ if (!openssl_pkey_export($res_key, $str_key) ||
+ !openssl_x509_export($res_crt, $str_crt)) {
+ return false;
+ }
+
+ // return our ca information
+ $ca['crt'] = base64_encode($str_crt);
+ $ca['prv'] = base64_encode($str_key);
+ $ca['serial'] = 0;
+
+ return true;
+}
+
+function ca_inter_create(& $ca, $keylen, $lifetime, $dn, $caref, $digest_alg = "sha256") {
+ // Create Intermediate Certificate Authority
+ $signing_ca =& lookup_ca($caref);
+ if (!$signing_ca) {
+ return false;
+ }
+
+ $signing_ca_res_crt = openssl_x509_read(base64_decode($signing_ca['crt']));
+ $signing_ca_res_key = openssl_pkey_get_private(array(0 => base64_decode($signing_ca['prv']) , 1 => ""));
+ if (!$signing_ca_res_crt || !$signing_ca_res_key) {
+ return false;
+ }
+ $signing_ca_serial = ++$signing_ca['serial'];
+
+ $args = array(
+ "x509_extensions" => "v3_ca",
+ "digest_alg" => $digest_alg,
+ "private_key_bits" => (int)$keylen,
+ "private_key_type" => OPENSSL_KEYTYPE_RSA,
+ "encrypt_key" => false);
+
+ // generate a new key pair
+ $res_key = openssl_pkey_new($args);
+ if (!$res_key) {
+ return false;
+ }
+
+ // generate a certificate signing request
+ $res_csr = openssl_csr_new($dn, $res_key, $args);
+ if (!$res_csr) {
+ return false;
+ }
+
+ // Sign the certificate
+ $res_crt = openssl_csr_sign($res_csr, $signing_ca_res_crt, $signing_ca_res_key, $lifetime, $args, $signing_ca_serial);
+ if (!$res_crt) {
+ return false;
+ }
+
+ // export our certificate data
+ if (!openssl_pkey_export($res_key, $str_key) ||
+ !openssl_x509_export($res_crt, $str_crt)) {
+ return false;
+ }
+
+ // return our ca information
+ $ca['crt'] = base64_encode($str_crt);
+ $ca['prv'] = base64_encode($str_key);
+ $ca['serial'] = 0;
+
+ return true;
+}
+
+function cert_import(& $cert, $crt_str, $key_str) {
+
+ $cert['crt'] = base64_encode($crt_str);
+ $cert['prv'] = base64_encode($key_str);
+
+ $subject = cert_get_subject($crt_str, false);
+ $issuer = cert_get_issuer($crt_str, false);
+
+ // Find my issuer unless self-signed
+ if ($issuer <> $subject) {
+ $issuer_crt =& lookup_ca_by_subject($issuer);
+ if ($issuer_crt) {
+ $cert['caref'] = $issuer_crt['refid'];
+ }
+ }
+ return true;
+}
+
+function cert_create(& $cert, $caref, $keylen, $lifetime, $dn, $type = "user", $digest_alg = "sha256") {
+
+ $cert['type'] = $type;
+
+ if ($type != "self-signed") {
+ $cert['caref'] = $caref;
+ $ca =& lookup_ca($caref);
+ if (!$ca) {
+ return false;
+ }
+
+ $ca_str_crt = base64_decode($ca['crt']);
+ $ca_str_key = base64_decode($ca['prv']);
+ $ca_res_crt = openssl_x509_read($ca_str_crt);
+ $ca_res_key = openssl_pkey_get_private(array(0 => $ca_str_key, 1 => ""));
+ if (!$ca_res_key) {
+ return false;
+ }
+ $ca_serial = ++$ca['serial'];
+ }
+
+ switch ($type) {
+ case "ca":
+ $cert_type = "v3_ca";
+ break;
+ case "server":
+ case "self-signed":
+ $cert_type = "server";
+ break;
+ default:
+ $cert_type = "usr_cert";
+ break;
+ }
+
+ // in case of using Subject Alternative Names use other sections (with postfix '_san')
+ // pass subjectAltName over environment variable 'SAN'
+ if ($dn['subjectAltName']) {
+ putenv("SAN={$dn['subjectAltName']}"); // subjectAltName can be set _only_ via configuration file
+ $cert_type .= '_san';
+ unset($dn['subjectAltName']);
+ }
+
+ $args = array(
+ "x509_extensions" => $cert_type,
+ "digest_alg" => $digest_alg,
+ "private_key_bits" => (int)$keylen,
+ "private_key_type" => OPENSSL_KEYTYPE_RSA,
+ "encrypt_key" => false);
+
+ // generate a new key pair
+ $res_key = openssl_pkey_new($args);
+ if (!$res_key) {
+ return false;
+ }
+
+ // If this is a self-signed cert, blank out the CA and sign with the cert's key
+ if ($type == "self-signed") {
+ $ca = null;
+ $ca_res_crt = null;
+ $ca_res_key = $res_key;
+ $ca_serial = 0;
+ $cert['type'] = "server";
+ }
+
+ // generate a certificate signing request
+ $res_csr = openssl_csr_new($dn, $res_key, $args);
+ if (!$res_csr) {
+ return false;
+ }
+
+ // sign the certificate using an internal CA
+ $res_crt = openssl_csr_sign($res_csr, $ca_res_crt, $ca_res_key, $lifetime,
+ $args, $ca_serial);
+ if (!$res_crt) {
+ return false;
+ }
+
+ // export our certificate data
+ if (!openssl_pkey_export($res_key, $str_key) ||
+ !openssl_x509_export($res_crt, $str_crt)) {
+ return false;
+ }
+
+ // return our certificate information
+ $cert['crt'] = base64_encode($str_crt);
+ $cert['prv'] = base64_encode($str_key);
+
+ return true;
+}
+
+function csr_generate(& $cert, $keylen, $dn, $digest_alg = "sha256") {
+
+ $args = array(
+ "x509_extensions" => "v3_req",
+ "digest_alg" => $digest_alg,
+ "private_key_bits" => (int)$keylen,
+ "private_key_type" => OPENSSL_KEYTYPE_RSA,
+ "encrypt_key" => false);
+
+ // generate a new key pair
+ $res_key = openssl_pkey_new($args);
+ if (!$res_key) {
+ return false;
+ }
+
+ // generate a certificate signing request
+ $res_csr = openssl_csr_new($dn, $res_key, $args);
+ if (!$res_csr) {
+ return false;
+ }
+
+ // export our request data
+ if (!openssl_pkey_export($res_key, $str_key) ||
+ !openssl_csr_export($res_csr, $str_csr)) {
+ return false;
+ }
+
+ // return our request information
+ $cert['csr'] = base64_encode($str_csr);
+ $cert['prv'] = base64_encode($str_key);
+
+ return true;
+}
+
+function csr_complete(& $cert, $str_crt) {
+
+ // return our request information
+ $cert['crt'] = base64_encode($str_crt);
+ unset($cert['csr']);
+
+ return true;
+}
+
+function csr_get_subject($str_crt, $decode = true) {
+
+ if ($decode) {
+ $str_crt = base64_decode($str_crt);
+ }
+
+ $components = openssl_csr_get_subject($str_crt);
+
+ if (empty($components) || !is_array($components)) {
+ return "unknown";
+ }
+
+ ksort($components);
+ foreach ($components as $a => $v) {
+ if (!strlen($subject)) {
+ $subject = "{$a}={$v}";
+ } else {
+ $subject = "{$a}={$v}, {$subject}";
+ }
+ }
+
+ return $subject;
+}
+
+function cert_get_subject($str_crt, $decode = true) {
+
+ if ($decode) {
+ $str_crt = base64_decode($str_crt);
+ }
+
+ $inf_crt = openssl_x509_parse($str_crt);
+ $components = $inf_crt['subject'];
+
+ if (empty($components) || !is_array($components)) {
+ return "unknown";
+ }
+
+ ksort($components);
+ foreach ($components as $a => $v) {
+ if (is_array($v)) {
+ ksort($v);
+ foreach ($v as $w) {
+ $asubject = "{$a}={$w}";
+ $subject = (strlen($subject)) ? "{$asubject}, {$subject}" : $asubject;
+ }
+ } else {
+ $asubject = "{$a}={$v}";
+ $subject = (strlen($subject)) ? "{$asubject}, {$subject}" : $asubject;
+ }
+ }
+
+ return $subject;
+}
+
+function cert_get_subject_array($crt) {
+ $str_crt = base64_decode($crt);
+ $inf_crt = openssl_x509_parse($str_crt);
+ $components = $inf_crt['subject'];
+
+ if (!is_array($components)) {
+ return;
+ }
+
+ $subject_array = array();
+
+ foreach ($components as $a => $v) {
+ $subject_array[] = array('a' => $a, 'v' => $v);
+ }
+
+ return $subject_array;
+}
+
+function cert_get_subject_hash($crt) {
+ $str_crt = base64_decode($crt);
+ $inf_crt = openssl_x509_parse($str_crt);
+ return $inf_crt['subject'];
+}
+
+function cert_get_issuer($str_crt, $decode = true) {
+
+ if ($decode) {
+ $str_crt = base64_decode($str_crt);
+ }
+
+ $inf_crt = openssl_x509_parse($str_crt);
+ $components = $inf_crt['issuer'];
+
+ if (empty($components) || !is_array($components)) {
+ return "unknown";
+ }
+
+ ksort($components);
+ foreach ($components as $a => $v) {
+ if (is_array($v)) {
+ ksort($v);
+ foreach ($v as $w) {
+ $aissuer = "{$a}={$w}";
+ $issuer = (strlen($issuer)) ? "{$aissuer}, {$issuer}" : $aissuer;
+ }
+ } else {
+ $aissuer = "{$a}={$v}";
+ $issuer = (strlen($issuer)) ? "{$aissuer}, {$issuer}" : $aissuer;
+ }
+ }
+
+ return $issuer;
+}
+
+/* this function works on x509 (crt), rsa key (prv), and req(csr) */
+function cert_get_modulus($str_crt, $decode = true, $type = "crt") {
+ if ($decode) {
+ $str_crt = base64_decode($str_crt);
+ }
+
+ $modulus = "";
+ if (in_array($type, array("crt", "prv", "csr"))) {
+ $type = str_replace(array("crt", "prv", "csr"), array("x509", "rsa", "req"), $type);
+ $modulus = exec("echo \"{$str_crt}\" | openssl {$type} -noout -modulus");
+ }
+ return $modulus;
+}
+function csr_get_modulus($str_crt, $decode = true) {
+ return cert_get_modulus($str_crt, $decode, "csr");
+}
+
+function cert_get_purpose($str_crt, $decode = true) {
+ if ($decode) {
+ $str_crt = base64_decode($str_crt);
+ }
+ $crt_details = openssl_x509_parse($str_crt);
+ $purpose = array();
+ $purpose['ca'] = (stristr($crt_details['extensions']['basicConstraints'], 'CA:TRUE') === false) ? 'No': 'Yes';
+ $purpose['server'] = ($crt_details['extensions']['nsCertType'] == "SSL Server") ? 'Yes': 'No';
+ return $purpose;
+}
+
+function cert_get_dates($str_crt, $decode = true) {
+ if ($decode) {
+ $str_crt = base64_decode($str_crt);
+ }
+ $crt_details = openssl_x509_parse($str_crt);
+ if ($crt_details['validFrom_time_t'] > 0) {
+ $start = date('r', $crt_details['validFrom_time_t']);
+ }
+ if ($crt_details['validTo_time_t'] > 0) {
+ $end = date('r', $crt_details['validTo_time_t']);
+ }
+ return array($start, $end);
+}
+
+function cert_get_serial($str_crt, $decode = true) {
+ if ($decode) {
+ $str_crt = base64_decode($str_crt);
+ }
+ $crt_details = openssl_x509_parse($str_crt);
+ if (isset($crt_details['serialNumber']) && !empty($crt_details['serialNumber'])) {
+ return $crt_details['serialNumber'];
+ } else {
+ return NULL;
+ }
+}
+
+function prv_get_modulus($str_crt, $decode = true) {
+ return cert_get_modulus($str_crt, $decode, "prv");
+}
+
+function is_user_cert($certref) {
+ global $config;
+ if (!is_array($config['system']['user'])) {
+ return;
+ }
+ foreach ($config['system']['user'] as $user) {
+ if (!is_array($user['cert'])) {
+ continue;
+ }
+ foreach ($user['cert'] as $cert) {
+ if ($certref == $cert) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+function is_openvpn_server_cert($certref) {
+ global $config;
+ if (!is_array($config['openvpn']['openvpn-server'])) {
+ return;
+ }
+ foreach ($config['openvpn']['openvpn-server'] as $ovpns) {
+ if ($ovpns['certref'] == $certref) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function is_openvpn_client_cert($certref) {
+ global $config;
+ if (!is_array($config['openvpn']['openvpn-client'])) {
+ return;
+ }
+ foreach ($config['openvpn']['openvpn-client'] as $ovpnc) {
+ if ($ovpnc['certref'] == $certref) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function is_ipsec_cert($certref) {
+ global $config;
+ if (!is_array($config['ipsec']['phase1'])) {
+ return;
+ }
+ foreach ($config['ipsec']['phase1'] as $ipsec) {
+ if ($ipsec['certref'] == $certref) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function is_webgui_cert($certref) {
+ global $config;
+ if (($config['system']['webgui']['ssl-certref'] == $certref) &&
+ ($config['system']['webgui']['protocol'] != "http")) {
+ return true;
+ }
+}
+
+function is_captiveportal_cert($certref) {
+ global $config;
+ if (!is_array($config['captiveportal'])) {
+ return;
+ }
+ foreach ($config['captiveportal'] as $portal) {
+ if (isset($portal['enable']) && isset($portal['httpslogin']) && ($portal['certref'] == $certref)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function cert_in_use($certref) {
+ return (is_webgui_cert($certref) ||
+ is_user_cert($certref) ||
+ is_openvpn_server_cert($certref) ||
+ is_openvpn_client_cert($certref) ||
+ is_ipsec_cert($certref) ||
+ is_captiveportal_cert($certref));
+}
+
+function crl_create(& $crl, $caref, $name, $serial = 0, $lifetime = 9999) {
+ global $config;
+ $ca =& lookup_ca($caref);
+ if (!$ca) {
+ return false;
+ }
+ $crl['descr'] = $name;
+ $crl['caref'] = $caref;
+ $crl['serial'] = $serial;
+ $crl['lifetime'] = $lifetime;
+ $crl['cert'] = array();
+ $crl_res = crl_update($crl);
+ $config['crl'][] = $crl;
+ return $crl_res;
+}
+
+function crl_update(& $crl) {
+ global $config;
+ $ca =& lookup_ca($crl['caref']);
+ if (!$ca) {
+ return false;
+ }
+ // If we have text but no certs, it was imported and cannot be updated.
+ if (($crl["method"] != "internal") && (!empty($crl['text']) && empty($crl['cert']))) {
+ return false;
+ }
+ $crl['serial']++;
+ $ca_str_crt = base64_decode($ca['crt']);
+ $ca_str_key = base64_decode($ca['prv']);
+ $crl_res = openssl_crl_new($ca_str_crt, $crl['serial'], $crl['lifetime']);
+ if (is_array($crl['cert']) && (count($crl['cert']) > 0)) {
+ foreach ($crl['cert'] as $cert) {
+ openssl_crl_revoke_cert($crl_res, base64_decode($cert["crt"]), $cert["revoke_time"], $cert["reason"]);
+ }
+ }
+ openssl_crl_export($crl_res, $crl_text, $ca_str_key);
+ $crl['text'] = base64_encode($crl_text);
+ return $crl_res;
+}
+
+function cert_revoke($cert, & $crl, $reason = OCSP_REVOKED_STATUS_UNSPECIFIED) {
+ global $config;
+ if (is_cert_revoked($cert, $crl['refid'])) {
+ return true;
+ }
+ // If we have text but no certs, it was imported and cannot be updated.
+ if (!is_crl_internal($crl)) {
+ return false;
+ }
+ $cert["reason"] = $reason;
+ $cert["revoke_time"] = time();
+ $crl["cert"][] = $cert;
+ crl_update($crl);
+ return true;
+}
+
+function cert_unrevoke($cert, & $crl) {
+ global $config;
+ if (!is_crl_internal($crl)) {
+ return false;
+ }
+ foreach ($crl['cert'] as $id => $rcert) {
+ if (($rcert['refid'] == $cert['refid']) || ($rcert['descr'] == $cert['descr'])) {
+ unset($crl['cert'][$id]);
+ if (count($crl['cert']) == 0) {
+ // Protect against accidentally switching the type to imported, for older CRLs
+ if (!isset($crl['method'])) {
+ $crl['method'] = "internal";
+ }
+ crl_update($crl);
+ } else {
+ crl_update($crl);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Compare two certificates to see if they match. */
+function cert_compare($cert1, $cert2) {
+ /* Ensure two certs are identical by first checking that their issuers match, then
+ subjects, then serial numbers, and finally the moduli. Anything less strict
+ could accidentally count two similar, but different, certificates as
+ being identical. */
+ $c1 = base64_decode($cert1['crt']);
+ $c2 = base64_decode($cert2['crt']);
+ if ((cert_get_issuer($c1, false) == cert_get_issuer($c2, false)) &&
+ (cert_get_subject($c1, false) == cert_get_subject($c2, false)) &&
+ (cert_get_serial($c1, false) == cert_get_serial($c2, false)) &&
+ (cert_get_modulus($c1, false) == cert_get_modulus($c2, false))) {
+ return true;
+ }
+ return false;
+}
+
+function is_cert_revoked($cert, $crlref = "") {
+ global $config;
+ if (!is_array($config['crl'])) {
+ return false;
+ }
+
+ if (!empty($crlref)) {
+ $crl = lookup_crl($crlref);
+ if (!is_array($crl['cert'])) {
+ return false;
+ }
+ foreach ($crl['cert'] as $rcert) {
+ if (cert_compare($rcert, $cert)) {
+ return true;
+ }
+ }
+ } else {
+ foreach ($config['crl'] as $crl) {
+ if (!is_array($crl['cert'])) {
+ continue;
+ }
+ foreach ($crl['cert'] as $rcert) {
+ if (cert_compare($rcert, $cert)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+function is_openvpn_server_crl($crlref) {
+ global $config;
+ if (!is_array($config['openvpn']['openvpn-server'])) {
+ return;
+ }
+ foreach ($config['openvpn']['openvpn-server'] as $ovpns) {
+ if (!empty($ovpns['crlref']) && ($ovpns['crlref'] == $crlref)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Keep this general to allow for future expansion. See cert_in_use() above.
+function crl_in_use($crlref) {
+ return (is_openvpn_server_crl($crlref));
+}
+
+function is_crl_internal($crl) {
+ return (!(!empty($crl['text']) && empty($crl['cert'])) || ($crl["method"] == "internal"));
+}
+
+function cert_get_cn($crt, $isref = false) {
+ /* If this is a certref, not an actual cert, look up the cert first */
+ if ($isref) {
+ $cert = lookup_cert($crt);
+ /* If it's not a valid cert, bail. */
+ if (!(is_array($cert) && !empty($cert['crt']))) {
+ return "";
+ }
+ $cert = $cert['crt'];
+ } else {
+ $cert = $crt;
+ }
+ $sub = cert_get_subject_array($cert);
+ if (is_array($sub)) {
+ foreach ($sub as $s) {
+ if (strtoupper($s['a']) == "CN") {
+ return $s['v'];
+ }
+ }
+ }
+ return "";
+}
+
+?>
diff --git a/src/etc/inc/config.console.inc b/src/etc/inc/config.console.inc
new file mode 100644
index 0000000..df3fa6f
--- /dev/null
+++ b/src/etc/inc/config.console.inc
@@ -0,0 +1,560 @@
+<?php
+/****h* pfSense/config
+ * NAME
+ * config.inc - Functions to manipulate config.xml
+ * DESCRIPTION
+ * This include contains various config.xml specific functions.
+ * HISTORY
+ * $Id$
+ ******
+
+ config.console.inc
+ Copyright (C) 2004-2010 Scott Ullrich
+ All rights reserved.
+
+ originally part of m0n0wall (http://m0n0.ch/wall)
+ 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.
+
+
+ pfSense_BUILDER_BINARIES: /sbin/mount /sbin/umount /sbin/halt /sbin/fsck
+ pfSense_MODULE: config
+*/
+
+function set_networking_interfaces_ports() {
+ global $noreboot;
+ global $config;
+ global $g;
+ global $fp;
+
+ $fp = fopen('php://stdin', 'r');
+
+ $memory = get_memory();
+ $physmem = $memory[0];
+ $realmem = $memory[1];
+
+ if ($physmem < $g['minimum_ram_warning']) {
+ echo "\n\n\n";
+ echo gettext("DANGER! WARNING! ACHTUNG!") . "\n\n";
+ printf(gettext("%s requires *AT LEAST* %s RAM to function correctly.%s"), $g['product_name'], $g['minimum_ram_warning_text'], "\n");
+ printf(gettext("Only (%s) MB RAM has been detected, with (%s) available to %s.%s"), $realmem, $physmem, $g['product_name'], "\n");
+ echo "\n" . gettext("Press ENTER to continue.") . " ";
+ fgets($fp);
+ echo "\n";
+ }
+
+ $iflist = get_interface_list();
+
+ /* Function flow is based on $key and $auto_assign or the lack thereof */
+ $key = null;
+
+ /* Only present auto interface option if running from LiveCD and interface mismatch*/
+ if ((preg_match("/cdrom/", $g['platform'])) && is_interface_mismatch()) {
+ $auto_assign = false;
+ }
+
+ echo <<<EOD
+
+Valid interfaces are:
+
+
+EOD;
+
+ if (!is_array($iflist)) {
+ echo gettext("No interfaces found!") . "\n";
+ $iflist = array();
+ } else {
+ foreach ($iflist as $iface => $ifa) {
+ echo sprintf("% -7s%s %s %s\n", $iface, $ifa['mac'],
+ $ifa['up'] ? " (up)" : "(down)", $ifa['dmesg']);
+ }
+ }
+
+ if ($auto_assign) {
+ echo <<<EOD
+
+ !!! LiveCD Detected: Auto Interface Option !!!!
+BEGIN MANUAL CONFIGURATION OR WE WILL PROCEED WITH AUTO CONFIGURATION.
+
+EOD;
+ }
+
+ echo <<<EOD
+
+Do you want to set up VLANs first?
+
+If you are not going to use VLANs, or only for optional interfaces, you should
+say no here and use the webConfigurator to configure VLANs later, if required.
+
+Do you want to set up VLANs now [y|n]?
+EOD;
+
+ if ($auto_assign) {
+ $key = timeout();
+ } else {
+ $key = chop(fgets($fp));
+ }
+
+ if (!isset($key) and $auto_assign) { // Auto Assign Interfaces
+ do {
+ echo <<<EOD
+
+ !!! Auto Assigning Interfaces !!!
+
+For installation purposes, you must plug in at least one NIC
+for the LAN connection. If you plug in a second NIC it will be
+assigned to WAN. Otherwise, we'll temporarily assign WAN to the
+next available NIC found regardless of activity. You should
+assign and configure the WAN interface according to your requirements
+
+If you haven't plugged in any network cables yet,
+now is the time to do so.
+We'll keep trying until you do.
+
+Searching for active interfaces...
+
+EOD;
+ unset($wanif, $lanif);
+
+ $media_iflist = $plugged_in = array();
+ $media_iflist = get_interface_list("media");
+ foreach ($media_iflist as $iface => $ifa) {
+ if ($ifa['up']) {
+ $plugged_in[] = $iface;
+ }
+ }
+
+ $lanif = array_shift($plugged_in);
+ $wanif = array_shift($plugged_in);
+
+ if (isset($lanif) && !isset($wanif)) {
+ foreach ($iflist as $iface => $ifa) {
+ if ($iface != $lanif) {
+ $wanif = $iface;
+ break;
+ }
+ }
+ }
+
+ echo <<<EOD
+
+Assigned WAN to : $wanif
+Assigned LAN to : $lanif
+
+If you don't like this assignment,
+press any key to go back to manual configuration.
+
+EOD;
+ $key = timeout(20);
+ if (isset($key)) {
+ return;
+ }
+ } while (!isset($wanif));
+
+ $config['system']['enablesshd'] = 'enabled';
+ $key = 'y';
+
+ } else {
+ //Manually assign interfaces
+ if (in_array($key, array('y', 'Y'))) {
+ vlan_setup();
+ }
+
+ if (is_array($config['vlans']['vlan']) && count($config['vlans']['vlan'])) {
+
+ echo "\n\n" . gettext("VLAN interfaces:") . "\n\n";
+ foreach ($config['vlans']['vlan'] as $vlan) {
+
+ echo sprintf("% -16s%s\n", "{$vlan['if']}_vlan{$vlan['tag']}",
+ "VLAN tag {$vlan['tag']}, parent interface {$vlan['if']}");
+
+ $iflist[$vlan['if'] . '_vlan' . $vlan['tag']] = array();
+ }
+ }
+
+ echo <<<EOD
+
+If you do not know the names of your interfaces, you may choose to use
+auto-detection. In that case, disconnect all interfaces now before
+hitting 'a' to initiate auto detection.
+
+EOD;
+
+ do {
+ echo "\n" . gettext("Enter the WAN interface name or 'a' for auto-detection:") . " ";
+ $wanif = chop(fgets($fp));
+ if ($wanif === "") {
+ return;
+ }
+ if ($wanif === "a") {
+ $wanif = autodetect_interface("WAN", $fp);
+ } else if (!array_key_exists($wanif, $iflist)) {
+ printf(gettext("%sInvalid interface name '%s'%s"), "\n", $wanif, "\n");
+ unset($wanif);
+ continue;
+ }
+ } while (!$wanif);
+
+ do {
+ printf(gettext("%sEnter the LAN interface name or 'a' for auto-detection %s" .
+ "NOTE: this enables full Firewalling/NAT mode.%s" .
+ "(or nothing if finished):%s"), "\n", "\n", "\n", " ");
+
+ $lanif = chop(fgets($fp));
+
+ if ($lanif == "exit") {
+ exit;
+ }
+
+ if ($lanif == "") {
+ /* It is OK to have just a WAN, without a LAN so break if the user does not want LAN. */
+ break;
+ }
+
+ if ($lanif === "a") {
+ $lanif = autodetect_interface("LAN", $fp);
+ } else if (!array_key_exists($lanif, $iflist)) {
+ printf(gettext("%sInvalid interface name '%s'%s"), "\n", $lanif, "\n");
+ unset($lanif);
+ continue;
+ }
+ } while (!$lanif);
+
+ /* optional interfaces */
+ $i = 0;
+ $optif = array();
+
+ if ($lanif <> "") {
+ while (1) {
+ if ($optif[$i]) {
+ $i++;
+ }
+ $io = $i + 1;
+
+ if ($config['interfaces']['opt' . $io]['descr']) {
+ printf(gettext("%sOptional interface %s description found: %s"), "\n", $io, $config['interfaces']['opt' . $io]['descr']);
+ }
+
+ printf(gettext("%sEnter the Optional %s interface name or 'a' for auto-detection%s" .
+ "(or nothing if finished):%s"), "\n", $io, "\n", " ");
+
+ $optif[$i] = chop(fgets($fp));
+
+ if ($optif[$i]) {
+ if ($optif[$i] === "a") {
+ $ad = autodetect_interface(gettext("Optional") . " " . $io, $fp);
+ if ($ad) {
+ $optif[$i] = $ad;
+ } else {
+ unset($optif[$i]);
+ }
+ } else if (!array_key_exists($optif[$i], $iflist)) {
+ printf(gettext("%sInvalid interface name '%s'%s"), "\n", $optif[$i], "\n");
+ unset($optif[$i]);
+ continue;
+ }
+ } else {
+ unset($optif[$i]);
+ break;
+ }
+ }
+ }
+
+ /* check for double assignments */
+ $ifarr = array_merge(array($lanif, $wanif), $optif);
+
+ for ($i = 0; $i < (count($ifarr)-1); $i++) {
+ for ($j = ($i+1); $j < count($ifarr); $j++) {
+ if ($ifarr[$i] == $ifarr[$j]) {
+ echo <<<EOD
+
+Error: you cannot assign the same interface name twice!
+
+EOD;
+ fclose($fp);
+ return;
+ }
+ }
+ }
+
+ echo "\n" . gettext("The interfaces will be assigned as follows:") . "\n\n";
+
+ echo "WAN -> " . $wanif . "\n";
+ if ($lanif != "") {
+ echo "LAN -> " . $lanif . "\n";
+ }
+ for ($i = 0; $i < count($optif); $i++) {
+ echo "OPT" . ($i+1) . " -> " . $optif[$i] . "\n";
+ }
+
+ echo <<<EOD
+
+Do you want to proceed [y|n]?
+EOD;
+ $key = chop(fgets($fp));
+ }
+
+ if (in_array($key, array('y', 'Y'))) {
+ if ($lanif) {
+ if (!is_array($config['interfaces']['lan'])) {
+ $config['interfaces']['lan'] = array();
+ }
+ $config['interfaces']['lan']['if'] = $lanif;
+ $config['interfaces']['lan']['enable'] = true;
+ } elseif (!platform_booting() && !$auto_assign) {
+
+echo <<<EODD
+
+You have chosen to remove the LAN interface.
+
+Would you like to remove the LAN IP address and
+unload the interface now? [y|n]?
+EODD;
+
+ if (strcasecmp(chop(fgets($fp)), "y") == 0) {
+ if (isset($config['interfaces']['lan']) && $config['interfaces']['lan']['if']) {
+ mwexec("/sbin/ifconfig " . $config['interfaces']['lan']['if'] . " delete");
+ }
+ }
+ if (isset($config['interfaces']['lan'])) {
+ unset($config['interfaces']['lan']);
+ }
+ if (isset($config['dhcpd']['lan'])) {
+ unset($config['dhcpd']['lan']);
+ }
+ if (isset($config['interfaces']['lan']['if'])) {
+ unset($config['interfaces']['lan']['if']);
+ }
+ if (isset($config['interfaces']['wan']['blockpriv'])) {
+ unset($config['interfaces']['wan']['blockpriv']);
+ }
+ if (isset($config['shaper'])) {
+ unset($config['shaper']);
+ }
+ if (isset($config['ezshaper'])) {
+ unset($config['ezshaper']);
+ }
+ if (isset($config['nat'])) {
+ unset($config['nat']);
+ }
+ } else {
+ if (isset($config['interfaces']['lan']['if'])) {
+ mwexec("/sbin/ifconfig " . $config['interfaces']['lan']['if'] . " delete");
+ }
+ if (isset($config['interfaces']['lan'])) {
+ unset($config['interfaces']['lan']);
+ }
+ if (isset($config['dhcpd']['lan'])) {
+ unset($config['dhcpd']['lan']);
+ }
+ if (isset($config['interfaces']['lan']['if'])) {
+ unset($config['interfaces']['lan']['if']);
+ }
+ if (isset($config['interfaces']['wan']['blockpriv'])) {
+ unset($config['interfaces']['wan']['blockpriv']);
+ }
+ if (isset($config['shaper'])) {
+ unset($config['shaper']);
+ }
+ if (isset($config['ezshaper'])) {
+ unset($config['ezshaper']);
+ }
+ if (isset($config['nat'])) {
+ unset($config['nat']);
+ }
+ }
+ if (preg_match($g['wireless_regex'], $lanif)) {
+ if (is_array($config['interfaces']['lan']) &&
+ !is_array($config['interfaces']['lan']['wireless'])) {
+ $config['interfaces']['lan']['wireless'] = array();
+ }
+ } else {
+ if (isset($config['interfaces']['lan'])) {
+ unset($config['interfaces']['lan']['wireless']);
+ }
+ }
+
+ if (!is_array($config['interfaces']['wan'])) {
+ $config['interfaces']['wan'] = array();
+ }
+ $config['interfaces']['wan']['if'] = $wanif;
+ $config['interfaces']['wan']['enable'] = true;
+ if (preg_match($g['wireless_regex'], $wanif)) {
+ if (is_array($config['interfaces']['wan']) &&
+ !is_array($config['interfaces']['wan']['wireless'])) {
+ $config['interfaces']['wan']['wireless'] = array();
+ }
+ } else {
+ if (isset($config['interfaces']['wan'])) {
+ unset($config['interfaces']['wan']['wireless']);
+ }
+ }
+
+ for ($i = 0; $i < count($optif); $i++) {
+ if (!is_array($config['interfaces']['opt' . ($i+1)])) {
+ $config['interfaces']['opt' . ($i+1)] = array();
+ }
+
+ $config['interfaces']['opt' . ($i+1)]['if'] = $optif[$i];
+
+ /* wireless interface? */
+ if (preg_match($g['wireless_regex'], $optif[$i])) {
+ if (!is_array($config['interfaces']['opt' . ($i+1)]['wireless'])) {
+ $config['interfaces']['opt' . ($i+1)]['wireless'] = array();
+ }
+ } else {
+ unset($config['interfaces']['opt' . ($i+1)]['wireless']);
+ }
+
+ if (empty($config['interfaces']['opt' . ($i+1)]['descr'])) {
+ $config['interfaces']['opt' . ($i+1)]['descr'] = "OPT" . ($i+1);
+ unset($config['interfaces']['opt' . ($i+1)]['enable']);
+ }
+ }
+
+ /* remove all other (old) optional interfaces */
+ for (; isset($config['interfaces']['opt' . ($i+1)]); $i++) {
+ unset($config['interfaces']['opt' . ($i+1)]);
+ }
+
+ printf(gettext("%sWriting configuration..."), "\n");
+ write_config("Console assignment of interfaces");
+ printf(gettext("done.%s"), "\n");
+
+ fclose($fp);
+
+ if (platform_booting()) {
+ return;
+ }
+
+ echo gettext("One moment while we reload the settings...");
+ echo gettext(" done!") . "\n";
+
+ touch("{$g['tmp_path']}/assign_complete");
+
+ }
+}
+
+function autodetect_interface($ifname, $fp) {
+ $iflist_prev = get_interface_list("media");
+ echo <<<EOD
+
+Connect the {$ifname} interface now and make sure that the link is up.
+Then press ENTER to continue.
+
+EOD;
+ fgets($fp);
+ $iflist = get_interface_list("media");
+
+ foreach ($iflist_prev as $ifn => $ifa) {
+ if (!$ifa['up'] && $iflist[$ifn]['up']) {
+ printf(gettext("Detected link-up on interface %s.%s"), $ifn, "\n");
+ return $ifn;
+ }
+ }
+
+ printf(gettext("No link-up detected.%s"), "\n");
+
+ return null;
+}
+
+function interfaces_setup() {
+ global $iflist, $config, $g, $fp;
+
+ $iflist = get_interface_list();
+}
+
+function vlan_setup() {
+ global $iflist, $config, $g, $fp;
+
+ $iflist = get_interface_list();
+
+ if (is_array($config['vlans']['vlan']) && count($config['vlans']['vlan'])) {
+
+ echo <<<EOD
+
+WARNING: all existing VLANs will be cleared if you proceed!
+
+Do you want to proceed [y|n]?
+EOD;
+
+ if (strcasecmp(chop(fgets($fp)), "y") != 0) {
+ return;
+ }
+ }
+
+ $config['vlans']['vlan'] = array();
+ echo "\n";
+
+ $vlanif = 0;
+
+ while (1) {
+ $vlan = array();
+
+ echo "\n\n" . gettext("VLAN Capable interfaces:") . "\n\n";
+ if (!is_array($iflist)) {
+ echo gettext("No interfaces found!") . "\n";
+ } else {
+ $vlan_capable = 0;
+ foreach ($iflist as $iface => $ifa) {
+ if (is_jumbo_capable($iface)) {
+ echo sprintf("% -8s%s%s\n", $iface, $ifa['mac'],
+ $ifa['up'] ? " (up)" : "");
+ $vlan_capable++;
+ }
+ }
+ }
+
+ if ($vlan_capable == 0) {
+ echo gettext("No VLAN capable interfaces detected.") . "\n";
+ return;
+ }
+
+ echo "\n" . gettext("Enter the parent interface name for the new VLAN (or nothing if finished):") . " ";
+ $vlan['if'] = chop(fgets($fp));
+
+ if ($vlan['if']) {
+ if (!array_key_exists($vlan['if'], $iflist) or
+ !is_jumbo_capable($vlan['if'])) {
+ printf(gettext("%sInvalid interface name '%s'%s"), "\n", $vlan['if'], "\n");
+ continue;
+ }
+ } else {
+ break;
+ }
+
+ echo gettext("Enter the VLAN tag (1-4094):") . " ";
+ $vlan['tag'] = chop(fgets($fp));
+ $vlan['vlanif'] = "{$vlan['if']}_vlan{$vlan['tag']}";
+ if (!is_numericint($vlan['tag']) || ($vlan['tag'] < 1) || ($vlan['tag'] > 4094)) {
+ printf(gettext("%sInvalid VLAN tag '%s'%s"), "\n", $vlan['tag'], "\n");
+ continue;
+ }
+
+ $config['vlans']['vlan'][] = $vlan;
+ $vlanif++;
+ }
+}
+
+?>
diff --git a/src/etc/inc/config.gui.inc b/src/etc/inc/config.gui.inc
new file mode 100644
index 0000000..56b5555
--- /dev/null
+++ b/src/etc/inc/config.gui.inc
@@ -0,0 +1,96 @@
+<?php
+/****h* pfSense/config
+ * NAME
+ * config.gui.inc - Functions to manipulate config.xml
+ * DESCRIPTION
+ * This include contains various config.xml specific functions.
+ * HISTORY
+ * $Id$
+ ******
+
+ config.gui.inc
+ Copyright (C) 2004-2010 Scott Ullrich
+ All rights reserved.
+
+ originally part of m0n0wall (http://m0n0.ch/wall)
+ 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.
+
+
+ pfSense_BUILDER_BINARIES: /sbin/mount /sbin/umount /sbin/halt /sbin/fsck
+ pfSense_MODULE: config
+*/
+
+require_once("globals.inc");
+
+/* do not load this file twice. */
+if ($config_parsed == true) {
+ return;
+} else {
+ $config_parsed = true;
+}
+
+/* include globals from notices.inc /utility/XML parser files */
+require_once('config.lib.inc');
+require_once("notices.inc");
+require_once("util.inc");
+require_once("IPv6.inc");
+if (file_exists("/cf/conf/use_xmlreader")) {
+ require_once("xmlreader.inc");
+} else {
+ require_once("xmlparse.inc");
+}
+require_once("crypt.inc");
+
+/* read platform */
+if (file_exists("{$g['etc_path']}/platform")) {
+ $g['platform'] = chop(file_get_contents("{$g['etc_path']}/platform"));
+} else {
+ $g['platform'] = "unknown";
+}
+
+/* if /debugging exists, lets set $debugging
+ so we can output more information */
+if (file_exists("/debugging")) {
+ $debugging = true;
+ $g['debug'] = true;
+}
+
+$config = parse_config();
+
+/* set timezone */
+$timezone = $config['system']['timezone'];
+if (!$timezone) {
+ $timezone = "Etc/UTC";
+}
+date_default_timezone_set("$timezone");
+
+if ($config_parsed == true) {
+ /* process packager manager custom rules */
+ if (is_dir("/usr/local/pkg/parse_config")) {
+ run_plugins("/usr/local/pkg/parse_config/");
+ }
+}
+
+?>
diff --git a/src/etc/inc/config.inc b/src/etc/inc/config.inc
new file mode 100644
index 0000000..4792ac3
--- /dev/null
+++ b/src/etc/inc/config.inc
@@ -0,0 +1,226 @@
+<?php
+/****h* pfSense/config
+ * NAME
+ * config.inc - Functions to manipulate config.xml
+ * DESCRIPTION
+ * This include contains various config.xml specific functions.
+ * HISTORY
+ * $Id$
+ ******
+
+ config.inc
+ Copyright (C) 2004-2010 Scott Ullrich
+ All rights reserved.
+
+ originally part of m0n0wall (http://m0n0.ch/wall)
+ 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.
+
+
+ pfSense_BUILDER_BINARIES: /sbin/mount /sbin/umount /sbin/halt /sbin/fsck
+ pfSense_MODULE: config
+*/
+
+if (!function_exists('platform_booting')) {
+ require_once('globals.inc');
+}
+
+/* do not load this file twice. */
+//if (in_array("/etc/inc/config.inc", get_included_files()))
+// return;
+
+// Set the memory limit to 128M on i386. When someone has something like 500+ tunnels
+// the parser needs quite a bit of ram. Do not remove this line unless you
+// know what you are doing. If in doubt, check with dev@ _/FIRST/_!
+if (!$ARCH) {
+ $ARCH = php_uname("m");
+}
+
+// Set memory limit to 256M on amd64.
+if ($ARCH == "amd64") {
+ ini_set("memory_limit", "256M");
+} else {
+ ini_set("memory_limit", "128M");
+}
+
+/* include globals from notices.inc /utility/XML parser files */
+require_once("notices.inc");
+require_once("util.inc");
+require_once("IPv6.inc");
+require_once('config.lib.inc');
+if (file_exists("/cf/conf/use_xmlreader")) {
+ require_once("xmlreader.inc");
+} else {
+ require_once("xmlparse.inc");
+}
+require_once("crypt.inc");
+
+/* read platform */
+if (file_exists("{$g['etc_path']}/platform")) {
+ $g['platform'] = chop(file_get_contents("{$g['etc_path']}/platform"));
+} else {
+ $g['platform'] = "unknown";
+}
+
+/* if /debugging exists, lets set $debugging
+ so we can output more information */
+if (file_exists("/debugging")) {
+ $debugging = true;
+ $g['debug'] = true;
+}
+
+if (platform_booting(true)) {
+ echo ".";
+ if (file_exists("/cf/conf/config.xml")) {
+ $config_contents = file_get_contents("/cf/conf/config.xml");
+ if (stristr($config_contents, "<m0n0wall>") == true) {
+ echo ".";
+ /* user has just upgraded from m0n0wall, replace root xml tags */
+ log_error(gettext("Upgrading m0n0wall configuration to pfSense... "));
+ $config_contents = str_replace("m0n0wall", "pfsense", $config_contents);
+ if (!config_validate("{$g['conf_path']}/config.xml")) {
+ log_error(gettext("ERROR! Could not convert m0n0wall -> pfsense in config.xml"));
+ }
+ conf_mount_rw();
+ file_put_contents("/cf/conf/config.xml", $config_contents);
+ conf_mount_ro();
+ }
+ unset($config_contents);
+ }
+ /* if our config file exists bail out, we're already set. */
+ else if (!file_exists($g['cf_conf_path'] . "/config.xml")) {
+ echo ".";
+ /* find the device where config.xml resides and write out an fstab */
+ unset($cfgdevice);
+ echo ".";
+ /* check if there's already an fstab (NFS booting?) */
+ if (!file_exists("{$g['etc_path']}/fstab")) {
+ echo ".";
+ if (strstr($g['platform'], "cdrom")) {
+ /* config is on floppy disk for CD-ROM version */
+ $cfgdevice = $cfgpartition = "fd0";
+ $_gb = exec('/sbin/dmesg -a', $dmesg);
+ if (preg_match("/da0/", $dmesg) == true) {
+ $cfgdevice = $cfgpartition = "da0" ;
+ if (mwexec("/sbin/mount -r /dev/{$cfgdevice} /cf")) {
+ /* could not mount, fallback to floppy */
+ $cfgdevice = $cfgpartition = "fd0";
+ }
+ }
+ unset($dmesg);
+ $cfgfstype = "msdosfs";
+ echo gettext("CDROM build") . "\n";
+ echo " " . gettext("CFG:") . " {$cfgpartition}\n";
+ echo " " . gettext("CFG:") . " {$cfgpartition}\n";
+ echo " " . gettext("TYPE:") . " {$cfgfstype}\n";
+ } else {
+ echo ".";
+ /* probe kernel known disks until we find one with config.xml */
+ $disks = explode(" ", get_single_sysctl("kern.disks"));
+ foreach ($disks as $mountdisk) {
+ /* skip mfs mounted filesystems */
+ if (strstr($mountdisk, "md")) {
+ continue;
+ }
+ if (mwexec("/sbin/mount -r /dev/{$mountdisk}a {$g['cf_path']}") == 0) {
+ if (file_exists("{$g['cf_conf_path']}/config.xml")) {
+ /* found it */
+ $cfgdevice = $mountdisk;
+ $cfgpartition = $cfgdevice . "a";
+ $cfgfstype = "ufs";
+ printf(gettext("Found configuration on %s.%s"), $cfgdevice, "\n");
+ }
+
+ mwexec("/sbin/umount -f {$g['cf_path']}");
+
+ if ($cfgdevice) {
+ break;
+ }
+ }
+ if (mwexec("/sbin/mount -r /dev/{$mountdisk}d {$g['cf_path']}") == 0) {
+ if (platform_booting()) {
+ echo ".";
+ }
+ if (file_exists("{$g['cf_conf_path']}/config.xml")) {
+ /* found it */
+ $cfgdevice = $mountdisk;
+ $cfgpartition = $cfgdevice . "d";
+ $cfgfstype = "ufs";
+ printf(gettext("Found configuration on %s.%s"), $cfgdevice, "\n");
+ }
+
+ mwexec("/sbin/umount -f {$g['cf_path']}");
+
+ if ($cfgdevice) {
+ break;
+ }
+ }
+ }
+ }
+ echo ".";
+ if (!$cfgdevice) {
+ $last_backup = discover_last_backup();
+ if ($last_backup) {
+ log_error(gettext("No config.xml found, attempting last known config restore."));
+ file_notice("config.xml", gettext("No config.xml found, attempting last known config restore."), "pfSenseConfigurator", "");
+ restore_backup("/cf/conf/backup/{$last_backup}");
+ } else {
+ log_error(gettext("No config.xml or config backups found, resetting to factory defaults."));
+ restore_backup('/conf.default/config.xml');
+ }
+ }
+
+ /* write device name to a file for rc.firmware */
+ file_put_contents("{$g['varetc_path']}/cfdevice", $cfgdevice . "\n");
+
+ /* write out an fstab */
+
+ $fstab = "/dev/{$cfgpartition} {$g['cf_path']} {$cfgfstype} ro,noatime 1 1\n";
+ $fstab .= "proc /proc procfs rw 0 0\n";
+ file_put_contents("{$g['etc_path']}/fstab", $fstab);
+ }
+ echo ".";
+ /* mount all filesystems */
+ mwexec("/sbin/mount -a");
+ }
+ echo ".";
+}
+
+$config = parse_config();
+
+/* set timezone */
+$timezone = $config['system']['timezone'];
+if (!$timezone) {
+ $timezone = "Etc/UTC";
+}
+date_default_timezone_set("$timezone");
+
+if ($config_parsed == true) {
+ /* process packager manager custom rules */
+ if (is_dir("/usr/local/pkg/parse_config")) {
+ run_plugins("/usr/local/pkg/parse_config/");
+ }
+}
+
+?>
diff --git a/src/etc/inc/config.lib.inc b/src/etc/inc/config.lib.inc
new file mode 100644
index 0000000..222d9d8
--- /dev/null
+++ b/src/etc/inc/config.lib.inc
@@ -0,0 +1,1020 @@
+<?php
+/****h* pfSense/config
+ * NAME
+ * config.lib.inc - Functions to manipulate config.xml
+ * DESCRIPTION
+ * This include contains various config.xml specific functions.
+ * HISTORY
+ * $Id$
+ ******
+
+ config.lib.inc
+ Ported from config.inc by Erik Kristensen
+ Copyright (C) 2004-2010 Scott Ullrich
+ All rights reserved.
+
+ originally part of m0n0wall (http://m0n0.ch/wall)
+ 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.
+
+
+ pfSense_BUILDER_BINARIES: /sbin/mount /sbin/umount /sbin/halt
+ pfSense_MODULE: config
+*/
+
+/****f* config/encrypted_configxml
+ * NAME
+ * encrypted_configxml - Checks to see if config.xml is encrypted and if so, prompts to unlock.
+ * INPUTS
+ * None
+ * RESULT
+ * $config - rewrites config.xml without encryption
+ ******/
+function encrypted_configxml() {
+ global $g, $config;
+
+ if (!file_exists($g['conf_path'] . "/config.xml")) {
+ return;
+ }
+
+ if (!platform_booting()) {
+ return;
+ }
+
+ $configtxt = file_get_contents($g['conf_path'] . "/config.xml");
+ if (tagfile_deformat($configtxt, $configtxt, "config.xml")) {
+ $fp = fopen('php://stdin', 'r');
+ $data = "";
+ echo "\n\n*** Encrypted config.xml detected ***\n";
+ while ($data == "") {
+ echo "\nEnter the password to decrypt config.xml: ";
+ $decrypt_password = chop(fgets($fp));
+ $data = decrypt_data($configtxt, $decrypt_password);
+ if (!strstr($data, "<pfsense>")) {
+ $data = "";
+ }
+ if ($data) {
+ $fd = fopen($g['conf_path'] . "/config.xml.tmp", "w");
+ fwrite($fd, $data);
+ fclose($fd);
+ exec("/bin/mv {$g['conf_path']}/config.xml.tmp {$g['conf_path']}/config.xml");
+ echo "\n" . gettext("Config.xml unlocked.") . "\n";
+ fclose($fp);
+ pfSense_fsync("{$g['conf_path']}/config.xml");
+ } else {
+ echo "\n" . gettext("Invalid password entered. Please try again.") . "\n";
+ }
+ }
+ }
+}
+
+/****f* config/parse_config
+ * NAME
+ * parse_config - Read in config.cache or config.xml if needed and return $config array
+ * INPUTS
+ * $parse - boolean to force parse_config() to read config.xml and generate config.cache
+ * RESULT
+ * $config - array containing all configuration variables
+ ******/
+function parse_config($parse = false) {
+ global $g, $config_parsed, $config_extra;
+
+ $lockkey = lock('config');
+ $config_parsed = false;
+
+ if (!file_exists("{$g['conf_path']}/config.xml") || filesize("{$g['conf_path']}/config.xml") == 0) {
+ $last_backup = discover_last_backup();
+ if ($last_backup) {
+ log_error(gettext("No config.xml found, attempting last known config restore."));
+ file_notice("config.xml", gettext("No config.xml found, attempting last known config restore."), "pfSenseConfigurator", "");
+ restore_backup("{$g['conf_path']}/backup/{$last_backup}");
+ } else {
+ unlock($lockkey);
+ die(gettext("Config.xml is corrupted and is 0 bytes. Could not restore a previous backup."));
+ }
+ }
+
+ if (platform_booting(true)) {
+ echo ".";
+ }
+
+ // Check for encrypted config.xml
+ encrypted_configxml();
+
+ if (!$parse) {
+ if (file_exists($g['tmp_path'] . '/config.cache')) {
+ $config = unserialize(file_get_contents($g['tmp_path'] . '/config.cache'));
+ if (is_null($config)) {
+ $parse = true;
+ }
+ } else {
+ $parse = true;
+ }
+ }
+ if ($parse == true) {
+ if (!file_exists($g['conf_path'] . "/config.xml")) {
+ if (platform_booting(true)) {
+ echo ".";
+ }
+ log_error("No config.xml found, attempting last known config restore.");
+ file_notice("config.xml", "No config.xml found, attempting last known config restore.", "pfSenseConfigurator", "");
+ $last_backup = discover_last_backup();
+ if ($last_backup) {
+ restore_backup("/cf/conf/backup/{$last_backup}");
+ } else {
+ log_error(gettext("Could not restore config.xml."));
+ unlock($lockkey);
+ die(gettext("Config.xml is corrupted and is 0 bytes. Could not restore a previous backup."));
+ }
+ }
+ $config = parse_xml_config($g['conf_path'] . '/config.xml', array($g['xml_rootobj'], 'pfsense'));
+ if ($config == -1) {
+ $last_backup = discover_last_backup();
+ if ($last_backup) {
+ restore_backup("/cf/conf/backup/{$last_backup}");
+ } else {
+ log_error(gettext("Could not restore config.xml."));
+ unlock($lockkey);
+ die("Config.xml is corrupted and is 0 bytes. Could not restore a previous backup.");
+ }
+ }
+ generate_config_cache($config);
+ }
+
+ if (platform_booting(true)) {
+ echo ".";
+ }
+
+ $config_parsed = true;
+ unlock($lockkey);
+
+ alias_make_table($config);
+
+ return $config;
+}
+
+/****f* config/generate_config_cache
+ * NAME
+ * generate_config_cache - Write serialized configuration to cache.
+ * INPUTS
+ * $config - array containing current firewall configuration
+ * RESULT
+ * boolean - true on completion
+ ******/
+function generate_config_cache($config) {
+ global $g, $config_extra;
+
+ $configcache = fopen($g['tmp_path'] . '/config.cache', "w");
+ fwrite($configcache, serialize($config));
+ fclose($configcache);
+ pfSense_fsync("{$g['tmp_path']}/config.cache");
+
+ unset($configcache);
+ /* Used for config.extra.xml */
+ if (file_exists($g['tmp_path'] . '/config.extra.cache') && $config_extra) {
+ $configcacheextra = fopen($g['tmp_path'] . '/config.extra.cache', "w");
+ fwrite($configcacheextra, serialize($config_extra));
+ fclose($configcacheextra);
+ pfSense_fsync("{$g['tmp_path']}/config.extra.cache");
+ unset($configcacheextra);
+ }
+}
+
+function discover_last_backup() {
+ $backups = glob('/cf/conf/backup/*.xml');
+ $last_backup = "";
+ $last_mtime = 0;
+ foreach ($backups as $backup) {
+ if (filemtime($backup) > $last_mtime) {
+ $last_mtime = filemtime($backup);
+ $last_backup = $backup;
+ }
+ }
+
+ return basename($last_backup);
+}
+
+function restore_backup($file) {
+ global $g;
+
+ if (file_exists($file)) {
+ conf_mount_rw();
+ unlink_if_exists("{$g['tmp_path']}/config.cache");
+ copy("$file", "/cf/conf/config.xml");
+ pfSense_fsync("/cf/conf/config.xml");
+ pfSense_fsync($g['conf_path']);
+ disable_security_checks();
+ log_error(sprintf(gettext('%1$s is restoring the configuration %2$s'), $g['product_name'], $file));
+ file_notice("config.xml", sprintf(gettext('%1$s is restoring the configuration %2$s'), $g['product_name'], $file), "pfSenseConfigurator", "");
+ conf_mount_ro();
+ }
+}
+
+/****f* config/parse_config_bootup
+ * NAME
+ * parse_config_bootup - Bootup-specific configuration checks.
+ * RESULT
+ * null
+ ******/
+function parse_config_bootup() {
+ global $config, $g;
+
+ if (platform_booting()) {
+ echo ".";
+ }
+
+ $lockkey = lock('config');
+ if (!file_exists("{$g['conf_path']}/config.xml")) {
+ if (platform_booting()) {
+ if (strstr($g['platform'], "cdrom")) {
+ /* try copying the default config. to the floppy */
+ echo gettext("Resetting factory defaults...") . "\n";
+ reset_factory_defaults(true);
+ if (!file_exists("{$g['conf_path']}/config.xml")) {
+ echo gettext("No XML configuration file found - using factory defaults.\n" .
+ "Make sure that the configuration floppy disk with the conf/config.xml\n" .
+ "file is inserted. If it isn't, your configuration changes will be lost\n" .
+ "on reboot.\n");
+ }
+ } else {
+ $last_backup = discover_last_backup();
+ if ($last_backup) {
+ log_error("No config.xml found, attempting last known config restore.");
+ file_notice("config.xml", gettext("No config.xml found, attempting last known config restore."), "pfSenseConfigurator", "");
+ restore_backup("/cf/conf/backup/{$last_backup}");
+ }
+ if (!file_exists("{$g['conf_path']}/config.xml")) {
+ echo sprintf(gettext("XML configuration file not found. %s cannot continue booting."), $g['product_name']) . "\n";
+ unlock($lockkey);
+ mwexec("/sbin/halt");
+ exit;
+ }
+ log_error("Last known config found and restored. Please double check your configuration file for accuracy.");
+ file_notice("config.xml", gettext("Last known config found and restored. Please double check your configuration file for accuracy."), "pfSenseConfigurator", "");
+ }
+ } else {
+ unlock($lockkey);
+ log_error(gettext("Could not find a usable configuration file! Exiting...."));
+ exit(0);
+ }
+ }
+
+ if (filesize("{$g['conf_path']}/config.xml") == 0) {
+ $last_backup = discover_last_backup();
+ if ($last_backup) {
+ log_error(gettext("No config.xml found, attempting last known config restore."));
+ file_notice("config.xml", gettext("No config.xml found, attempting last known config restore."), "pfSenseConfigurator", "");
+ restore_backup("{$g['conf_path']}/backup/{$last_backup}");
+ } else {
+ unlock($lockkey);
+ die(gettext("Config.xml is corrupted and is 0 bytes. Could not restore a previous backup."));
+ }
+ }
+ unlock($lockkey);
+
+ $config = parse_config(true);
+
+ if ((float)$config['version'] > (float)$g['latest_config']) {
+ echo <<<EOD
+
+
+*******************************************************************************
+* WARNING! *
+* The current configuration has been created with a newer version of {$g['product_name']} *
+* than this one! This can lead to serious misbehavior and even security *
+* holes! You are urged to either upgrade to a newer version of {$g['product_name']} or *
+* revert to the default configuration immediately! *
+*******************************************************************************
+
+
+EOD;
+ }
+
+ /* make alias table (for faster lookups) */
+ alias_make_table($config);
+}
+
+/****f* config/conf_mount_rw
+ * NAME
+ * conf_mount_rw - Mount filesystems read/write.
+ * RESULT
+ * null
+ ******/
+/* mount flash card read/write */
+function conf_mount_rw() {
+ global $g, $config;
+
+ /* do not mount on cdrom platform */
+ if ($g['platform'] == "cdrom" or $g['platform'] == "pfSense") {
+ return;
+ }
+
+ if ((refcount_reference(1000) > 1) && is_writable("/")) {
+ return;
+ }
+
+ $status = mwexec("/sbin/mount -u -w -o sync,noatime {$g['cf_path']}");
+ if ($status <> 0) {
+ if (platform_booting()) {
+ echo gettext("/cf Filesystem is dirty.") . "\n";
+ }
+ $status = mwexec("/sbin/mount -u -w -o sync,noatime {$g['cf_path']}");
+ }
+
+ /* if the platform is soekris or wrap or pfSense, lets mount the
+ * compact flash cards root.
+ */
+ $status = mwexec("/sbin/mount -u -w -o sync,noatime /");
+ /* we could not mount this correctly. */
+ if ($status <> 0) {
+ log_error(gettext("/ File system is dirty."));
+ $status = mwexec("/sbin/mount -u -w -o sync,noatime /");
+ }
+
+ mark_subsystem_dirty('mount');
+}
+
+/****f* config/conf_mount_ro
+ * NAME
+ * conf_mount_ro - Mount filesystems readonly.
+ * RESULT
+ * null
+ ******/
+function conf_mount_ro() {
+ global $g, $config;
+
+ /* Do not trust $g['platform'] since this can be clobbered during factory reset. */
+ $platform = trim(file_get_contents("/etc/platform"));
+ /* do not umount on cdrom or pfSense platforms */
+ if ($platform == "cdrom" or $platform == "pfSense") {
+ return;
+ }
+
+ if (refcount_unreference(1000) > 0) {
+ return;
+ }
+
+ if (isset($config['system']['nanobsd_force_rw'])) {
+ return;
+ }
+
+ if (platform_booting()) {
+ return;
+ }
+
+ clear_subsystem_dirty('mount');
+ /* sync data, then force a remount of /cf */
+ pfSense_fsync($g['cf_path']);
+ mwexec("/sbin/mount -u -r -f -o sync,noatime {$g['cf_path']}");
+ mwexec("/sbin/mount -u -r -f -o sync,noatime /");
+}
+
+/****f* config/convert_config
+ * NAME
+ * convert_config - Attempt to update config.xml.
+ * DESCRIPTION
+ * convert_config() reads the current global configuration
+ * and attempts to convert it to conform to the latest
+ * config.xml version. This allows major formatting changes
+ * to be made with a minimum of breakage.
+ * RESULT
+ * null
+ ******/
+/* convert configuration, if necessary */
+function convert_config() {
+ global $config, $g;
+ $now = date("H:i:s");
+ log_error(sprintf(gettext("Start Configuration upgrade at %s, set execution timeout to 15 minutes"), $now));
+ //ini_set("max_execution_time", "900");
+
+ /* special case upgrades */
+ /* fix every minute crontab bogons entry */
+ if (is_array($config['cron'])) {
+ $cron_item_count = count($config['cron']['item']);
+ for ($x = 0; $x < $cron_item_count; $x++) {
+ if (stristr($config['cron']['item'][$x]['command'], "rc.update_bogons.sh")) {
+ if ($config['cron']['item'][$x]['hour'] == "*") {
+ $config['cron']['item'][$x]['hour'] = "3";
+ write_config(gettext("Updated bogon update frequency to 3am"));
+ log_error(gettext("Updated bogon update frequency to 3am"));
+ }
+ }
+ }
+ }
+ if ($config['version'] == $g['latest_config']) {
+ return; /* already at latest version */
+ }
+
+ // Save off config version
+ $prev_version = $config['version'];
+
+ include_once('auth.inc');
+ include_once('upgrade_config.inc');
+ if (file_exists("/etc/inc/upgrade_config_custom.inc")) {
+ include_once("upgrade_config_custom.inc");
+ }
+ /* Loop and run upgrade_VER_to_VER() until we're at current version */
+ while ($config['version'] < $g['latest_config']) {
+ $cur = $config['version'] * 10;
+ $next = $cur + 1;
+ $migration_function = sprintf('upgrade_%03d_to_%03d', $cur, $next);
+ if (function_exists($migration_function)) {
+ $migration_function();
+ }
+ $migration_function = "{$migration_function}_custom";
+ if (function_exists($migration_function)) {
+ $migration_function();
+ }
+ $config['version'] = sprintf('%.1f', $next / 10);
+ if (platform_booting()) {
+ echo ".";
+ }
+ }
+
+ $now = date("H:i:s");
+ log_error(sprintf(gettext("Ended Configuration upgrade at %s"), $now));
+
+ if ($prev_version != $config['version']) {
+ write_config(sprintf(gettext('Upgraded config version level from %1$s to %2$s'), $prev_version, $config['version']));
+ }
+}
+
+/****f* config/safe_write_file
+ * NAME
+ * safe_write_file - Write a file out atomically
+ * DESCRIPTION
+ * safe_write_file() Writes a file out atomically by first writing to a
+ * temporary file of the same name but ending with the pid of the current
+ * process, them renaming the temporary file over the original.
+ * INPUTS
+ * $filename - string containing the filename of the file to write
+ * $content - string containing the file content to write to file
+ * $force_binary - boolean denoting whether we should force binary
+ * mode writing.
+ * RESULT
+ * boolean - true if successful, false if not
+ ******/
+function safe_write_file($file, $content, $force_binary) {
+ $tmp_file = $file . "." . getmypid();
+ $write_mode = $force_binary ? "wb" : "w";
+
+ $fd = fopen($tmp_file, $write_mode);
+ if (!$fd) {
+ // Unable to open temporary file for writing
+ return false;
+ }
+ if (!fwrite($fd, $content)) {
+ // Unable to write to temporary file
+ fclose($fd);
+ return false;
+ }
+ fflush($fd);
+ fclose($fd);
+
+ if (!pfSense_fsync($tmp_file) || !rename($tmp_file, $file)) {
+ // Unable to move temporary file to original
+ @unlink($tmp_file);
+ return false;
+ }
+
+ // Sync file before returning
+ return pfSense_fsync($file);
+}
+
+/****f* config/write_config
+ * NAME
+ * write_config - Backup and write the firewall configuration.
+ * DESCRIPTION
+ * write_config() handles backing up the current configuration,
+ * applying changes, and regenerating the configuration cache.
+ * INPUTS
+ * $desc - string containing the a description of configuration changes
+ * $backup - boolean: do not back up current configuration if false.
+ * RESULT
+ * null
+ ******/
+/* save the system configuration */
+function write_config($desc="Unknown", $backup = true) {
+ global $config, $g;
+
+ if (!empty($_SERVER['REMOTE_ADDR'])) {
+ if (!session_id()) {
+ @session_start();
+ }
+ if (!empty($_SESSION['Username']) && ($_SESSION['Username'] != "admin")) {
+ $user = getUserEntry($_SESSION['Username']);
+ if (is_array($user) && userHasPrivilege($user, "user-config-readonly")) {
+ session_commit();
+ return false;
+ }
+ }
+ }
+
+ if (!isset($argc)) {
+ session_commit();
+ }
+
+ if ($backup) {
+ backup_config();
+ }
+
+ $config['revision'] = make_config_revision_entry($desc);
+
+ conf_mount_rw();
+ $lockkey = lock('config', LOCK_EX);
+
+ /* generate configuration XML */
+ $xmlconfig = dump_xml_config($config, $g['xml_rootobj']);
+
+ /* write new configuration */
+ if (!safe_write_file("{$g['cf_conf_path']}/config.xml", $xmlconfig, false)) {
+ log_error(gettext("WARNING: Config contents could not be saved. Could not open file!"));
+ unlock($lockkey);
+ file_notice("config.xml", sprintf(gettext("Unable to open %s/config.xml for writing in write_config()%s"), $g['cf_conf_path'], "\n"));
+ return -1;
+ }
+
+ cleanup_backupcache(true);
+
+ /* re-read configuration */
+ /* NOTE: We assume that the file can be parsed since we wrote it. */
+ $config = parse_xml_config("{$g['conf_path']}/config.xml", $g['xml_rootobj']);
+ if ($config == -1) {
+ copy("{$g['conf_path']}/config.xml", "{$g['conf_path']}/config.xml.bad");
+ $last_backup = discover_last_backup();
+ if ($last_backup) {
+ restore_backup("/cf/conf/backup/{$last_backup}");
+ $config = parse_xml_config("{$g['conf_path']}/config.xml", $g['xml_rootobj']);
+ if (platform_booting()) {
+ echo "\n\n ************** WARNING **************";
+ echo "\n\n Configuration could not be validated. A previous configuration was restored. \n";
+ echo "\n The failed configuration file has been saved as {$g['conf_path']}/config.xml.bad \n\n";
+ }
+ } else {
+ log_error(gettext("Could not restore config.xml."));
+ }
+ } else {
+ generate_config_cache($config);
+ }
+
+ unlock($lockkey);
+
+ unlink_if_exists("/usr/local/pkg/pf/carp_sync_client.php");
+
+ /* tell kernel to sync fs data */
+ conf_mount_ro();
+
+ /* sync carp entries to other firewalls */
+ carp_sync_client();
+
+ if (is_dir("/usr/local/pkg/write_config")) {
+ /* process packager manager custom rules */
+ run_plugins("/usr/local/pkg/write_config/");
+ }
+
+ return $config;
+}
+
+/****f* config/reset_factory_defaults
+ * NAME
+ * reset_factory_defaults - Reset the system to its default configuration.
+ * RESULT
+ * integer - indicates completion
+ ******/
+function reset_factory_defaults($lock = false) {
+ global $g;
+
+ conf_mount_rw();
+ if (!$lock) {
+ $lockkey = lock('config', LOCK_EX);
+ }
+
+ /* create conf directory, if necessary */
+ safe_mkdir("{$g['cf_conf_path']}");
+
+ /* clear out /conf */
+ $dh = opendir($g['conf_path']);
+ while ($filename = readdir($dh)) {
+ if (($filename != ".") && ($filename != "..")) {
+ unlink_if_exists($g['conf_path'] . "/" . $filename);
+ }
+ }
+ closedir($dh);
+ unlink_if_exists($g['tmp_path'] . "/config.cache");
+
+ /* copy default configuration */
+ copy("{$g['conf_default_path']}/config.xml", "{$g['conf_path']}/config.xml");
+
+ disable_security_checks();
+
+ /* call the wizard */
+ touch("/conf/trigger_initial_wizard");
+ if (!$lock) {
+ unlock($lockkey);
+ }
+ conf_mount_ro();
+ setup_serial_port();
+ return 0;
+}
+
+function config_restore($conffile) {
+ global $config, $g;
+
+ if (!file_exists($conffile)) {
+ return 1;
+ }
+
+ backup_config();
+
+ conf_mount_rw();
+
+ $lockkey = lock('config', LOCK_EX);
+
+ unlink_if_exists("{$g['tmp_path']}/config.cache");
+ copy($conffile, "{$g['cf_conf_path']}/config.xml");
+
+ disable_security_checks();
+
+ unlock($lockkey);
+
+ $config = parse_config(true);
+
+ conf_mount_ro();
+
+ write_config(gettext("Reverted to") . " " . array_pop(explode("/", $conffile)) . ".", false);
+
+ return 0;
+}
+
+function config_install($conffile) {
+ global $config, $g;
+
+ if (!file_exists($conffile)) {
+ return 1;
+ }
+
+ if (!config_validate("{$conffile}")) {
+ return 1;
+ }
+
+ if (platform_booting()) {
+ echo gettext("Installing configuration...") . "\n";
+ } else {
+ log_error(gettext("Installing configuration ...."));
+ }
+
+ conf_mount_rw();
+ $lockkey = lock('config', LOCK_EX);
+
+ copy($conffile, "{$g['conf_path']}/config.xml");
+
+ disable_security_checks();
+
+ /* unlink cache file if it exists */
+ if (file_exists("{$g['tmp_path']}/config.cache")) {
+ unlink("{$g['tmp_path']}/config.cache");
+ }
+
+ unlock($lockkey);
+ conf_mount_ro();
+
+ return 0;
+}
+
+/*
+ * Disable security checks for DNS rebind and HTTP referrer until next time
+ * they pass (or reboot), to aid in preventing accidental lockout when
+ * restoring settings like hostname, domain, IP addresses, and settings
+ * related to the DNS rebind and HTTP referrer checks.
+ * Intended for use when restoring a configuration or directly
+ * modifying config.xml without an unconditional reboot.
+ */
+function disable_security_checks() {
+ global $g;
+ touch("{$g['tmp_path']}/disable_security_checks");
+}
+
+/* Restores security checks. Should be called after all succeed. */
+function restore_security_checks() {
+ global $g;
+ unlink_if_exists("{$g['tmp_path']}/disable_security_checks");
+}
+
+/* Returns status of security check temporary disable. */
+function security_checks_disabled() {
+ global $g;
+ return file_exists("{$g['tmp_path']}/disable_security_checks");
+}
+
+function config_validate($conffile) {
+
+ global $g, $xmlerr;
+
+ $xml_parser = xml_parser_create();
+
+ if (!($fp = fopen($conffile, "r"))) {
+ $xmlerr = gettext("XML error: unable to open file");
+ return false;
+ }
+
+ while ($data = fread($fp, 4096)) {
+ if (!xml_parse($xml_parser, $data, feof($fp))) {
+ $xmlerr = sprintf(gettext('%1$s at line %2$d'),
+ xml_error_string(xml_get_error_code($xml_parser)),
+ xml_get_current_line_number($xml_parser));
+ return false;
+ }
+ }
+ xml_parser_free($xml_parser);
+
+ fclose($fp);
+
+ return true;
+}
+
+function cleanup_backupcache($lock = false) {
+ global $g;
+ $i = false;
+
+ $revisions = get_config_backup_count();
+
+ if (!$lock) {
+ $lockkey = lock('config');
+ }
+
+ conf_mount_rw();
+
+ $backups = get_backups();
+ if ($backups) {
+ $baktimes = $backups['versions'];
+ unset($backups['versions']);
+ } else {
+ $backups = array();
+ $baktimes = array();
+ }
+ $newbaks = array();
+ $bakfiles = glob($g['cf_conf_path'] . "/backup/config-*");
+ $tocache = array();
+
+ foreach ($bakfiles as $backup) { // Check for backups in the directory not represented in the cache.
+ $backupsize = filesize($backup);
+ if ($backupsize == 0) {
+ unlink($backup);
+ continue;
+ }
+ $backupexp = explode('-', $backup);
+ $backupexp = explode('.', array_pop($backupexp));
+ $tocheck = array_shift($backupexp);
+ unset($backupexp);
+ if (!in_array($tocheck, $baktimes)) {
+ $i = true;
+ if (platform_booting()) {
+ echo ".";
+ }
+ $newxml = parse_xml_config($backup, array($g['xml_rootobj'], 'pfsense'));
+ if ($newxml == "-1") {
+ log_error(sprintf(gettext("The backup cache file %s is corrupted. Unlinking."), $backup));
+ unlink($backup);
+ log_error(sprintf(gettext("The backup cache file %s is corrupted. Unlinking."), $backup));
+ continue;
+ }
+ if ($newxml['revision']['description'] == "") {
+ $newxml['revision']['description'] = "Unknown";
+ }
+ if ($newxml['version'] == "") {
+ $newxml['version'] = "?";
+ }
+ $tocache[$tocheck] = array('description' => $newxml['revision']['description'], 'version' => $newxml['version'], 'filesize' => $backupsize);
+ }
+ }
+ foreach ($backups as $checkbak) {
+ if (count(preg_grep('/' . $checkbak['time'] . '/i', $bakfiles)) != 0) {
+ $newbaks[] = $checkbak;
+ } else {
+ $i = true;
+ if (platform_booting()) print " " . $tocheck . "r";
+ }
+ }
+ foreach ($newbaks as $todo) {
+ $tocache[$todo['time']] = array('description' => $todo['description'], 'version' => $todo['version'], 'filesize' => $todo['filesize']);
+ }
+ if (is_int($revisions) and (count($tocache) > $revisions)) {
+ $toslice = array_slice(array_keys($tocache), 0, $revisions);
+ foreach ($toslice as $sliced) {
+ $newcache[$sliced] = $tocache[$sliced];
+ }
+ foreach ($tocache as $version => $versioninfo) {
+ if (!in_array($version, array_keys($newcache))) {
+ unlink_if_exists($g['conf_path'] . '/backup/config-' . $version . '.xml');
+ }
+ }
+ $tocache = $newcache;
+ }
+ $bakout = fopen($g['cf_conf_path'] . '/backup/backup.cache', "w");
+ fwrite($bakout, serialize($tocache));
+ fclose($bakout);
+ pfSense_fsync("{$g['cf_conf_path']}/backup/backup.cache");
+ conf_mount_ro();
+
+ if (!$lock) {
+ unlock($lockkey);
+ }
+}
+
+function get_backups() {
+ global $g;
+ if (file_exists("{$g['cf_conf_path']}/backup/backup.cache")) {
+ $confvers = unserialize(file_get_contents("{$g['cf_conf_path']}/backup/backup.cache"));
+ $bakvers = array_keys($confvers);
+ $toreturn = array();
+ sort($bakvers);
+ // $bakvers = array_reverse($bakvers);
+ foreach (array_reverse($bakvers) as $bakver) {
+ $toreturn[] = array('time' => $bakver, 'description' => $confvers[$bakver]['description'], 'version' => $confvers[$bakver]['version'], 'filesize' => $confvers[$bakver]['filesize']);
+ }
+ } else {
+ return false;
+ }
+ $toreturn['versions'] = $bakvers;
+ return $toreturn;
+}
+
+function backup_config() {
+ global $config, $g;
+
+ if ($g['platform'] == "cdrom") {
+ return;
+ }
+
+ conf_mount_rw();
+
+ /* Create backup directory if needed */
+ safe_mkdir("{$g['cf_conf_path']}/backup");
+ if ($config['revision']['time'] == "") {
+ $baktime = 0;
+ } else {
+ $baktime = $config['revision']['time'];
+ }
+
+ if ($config['revision']['description'] == "") {
+ $bakdesc = "Unknown";
+ } else {
+ $bakdesc = $config['revision']['description'];
+ }
+
+ $bakver = ($config['version'] == "") ? "?" : $config['version'];
+ $bakfilename = $g['cf_conf_path'] . '/backup/config-' . $baktime . '.xml';
+ copy($g['cf_conf_path'] . '/config.xml', $bakfilename);
+
+ if (file_exists($g['cf_conf_path'] . '/backup/backup.cache')) {
+ $backupcache = unserialize(file_get_contents($g['cf_conf_path'] . '/backup/backup.cache'));
+ } else {
+ $backupcache = array();
+ }
+ $backupcache[$baktime] = array('description' => $bakdesc, 'version' => $bakver, 'filesize' => filesize($bakfilename));
+ $bakout = fopen($g['cf_conf_path'] . '/backup/backup.cache', "w");
+ fwrite($bakout, serialize($backupcache));
+ fclose($bakout);
+ pfSense_fsync("{$g['cf_conf_path']}/backup/backup.cache");
+
+ conf_mount_ro();
+
+ return true;
+}
+
+function set_device_perms() {
+ $devices = array(
+ 'pf' => array(
+ 'user' => 'root',
+ 'group' => 'proxy',
+ 'mode' => 0660),
+ );
+
+ foreach ($devices as $name => $attr) {
+ $path = "/dev/$name";
+ if (file_exists($path)) {
+ chown($path, $attr['user']);
+ chgrp($path, $attr['group']);
+ chmod($path, $attr['mode']);
+ }
+ }
+}
+
+function get_config_user() {
+ if (empty($_SESSION["Username"])) {
+ $username = getenv("USER");
+ if (empty($conuser) || $conuser == "root") {
+ $username = "(system)";
+ }
+ } else {
+ $username = $_SESSION["Username"];
+ }
+
+ if (!empty($_SERVER['REMOTE_ADDR'])) {
+ $username .= '@' . $_SERVER['REMOTE_ADDR'];
+ }
+
+ return $username;
+}
+
+function make_config_revision_entry($desc = null, $override_user = null) {
+ if (empty($override_user)) {
+ $username = get_config_user();
+ } else {
+ $username = $override_user;
+ }
+
+ $revision = array();
+
+ if (time() > mktime(0, 0, 0, 9, 1, 2004)) { /* make sure the clock settings are plausible */
+ $revision['time'] = time();
+ }
+
+ /* Log the running script so it's not entirely unlogged what changed */
+ if ($desc == "Unknown") {
+ $desc = sprintf(gettext("%s made unknown change"), $_SERVER['SCRIPT_NAME']);
+ }
+ if (!empty($desc)) {
+ $revision['description'] = "{$username}: " . $desc;
+ }
+ $revision['username'] = $username;
+ return $revision;
+}
+
+function get_config_backup_count() {
+ global $config, $g;
+ if (isset($config['system']['backupcount']) && is_numeric($config['system']['backupcount']) && ($config['system']['backupcount'] >= 0)) {
+ return intval($config['system']['backupcount']);
+ } elseif ($g['platform'] == "nanobsd") {
+ return 5;
+ } else {
+ return 30;
+ }
+}
+
+function pfSense_clear_globals() {
+ global $config, $FilterIfList, $GatewaysList, $filterdns, $aliases, $aliastable;
+
+ $error = error_get_last();
+
+ if ($error !== NULL) {
+ if ($error['type'] == E_ERROR) {
+ $errorstr = "PHP ERROR: Type: {$error['type']}, File: {$error['file']}, Line: {$error['line']}, Message: {$error['message']}";
+ print($errorstr);
+ log_error($errorstr);
+ } else if ($error['type'] != E_NOTICE) {
+ $errorstr = "PHP WARNING: Type: {$error['type']}, File: {$error['file']}, Line: {$error['line']}, Message: {$error['message']}";
+ // XXX: comment out for now, should re-enable post-2.2
+ //print($errorstr);
+ //log_error($errorstr);
+ }
+ }
+
+ if (isset($FilterIfList)) {
+ unset($FilterIfList);
+ }
+
+ if (isset($GatewaysList)) {
+ unset($GatewaysList);
+ }
+
+ /* Used for the hostname dns resolver */
+ if (isset($filterdns)) {
+ unset($filterdns);
+ }
+
+ /* Used for aliases and interface macros */
+ if (isset($aliases)) {
+ unset($aliases);
+ }
+ if (isset($aliastable)) {
+ unset($aliastable);
+ }
+
+ unset($config);
+}
+
+register_shutdown_function('pfSense_clear_globals');
+
+?>
diff --git a/src/etc/inc/cram_md5_sasl_client.inc b/src/etc/inc/cram_md5_sasl_client.inc
new file mode 100644
index 0000000..69bd625
--- /dev/null
+++ b/src/etc/inc/cram_md5_sasl_client.inc
@@ -0,0 +1,67 @@
+<?php
+/*
+ * cram_md5_sasl_client.php
+ *
+ * @(#) $Id: cram_md5_sasl_client.php,v 1.3 2004/11/17 08:00:37 mlemos Exp $
+ *
+ */
+
+define("SASL_CRAM_MD5_STATE_START", 0);
+define("SASL_CRAM_MD5_STATE_RESPOND_CHALLENGE", 1);
+define("SASL_CRAM_MD5_STATE_DONE", 2);
+
+class cram_md5_sasl_client_class
+{
+ var $credentials=array();
+ var $state=SASL_CRAM_MD5_STATE_START;
+
+ Function Initialize(&$client)
+ {
+ return(1);
+ }
+
+ Function HMACMD5($key,$text)
+ {
+ $key=(strlen($key)<64 ? str_pad($key,64,"\0") : substr($key,0,64));
+ return(md5((str_repeat("\x5c", 64)^$key).pack("H32", md5((str_repeat("\x36", 64)^$key).$text))));
+ }
+
+ Function Start(&$client, &$message, &$interactions)
+ {
+ if($this->state!=SASL_CRAM_MD5_STATE_START)
+ {
+ $client->error="CRAM-MD5 authentication state is not at the start";
+ return(SASL_FAIL);
+ }
+ $this->credentials=array(
+ "user"=>"",
+ "password"=>""
+ );
+ $defaults=array();
+ $status=$client->GetCredentials($this->credentials,$defaults,$interactions);
+ if($status==SASL_CONTINUE)
+ $this->state=SASL_CRAM_MD5_STATE_RESPOND_CHALLENGE;
+ Unset($message);
+ return($status);
+ }
+
+ Function Step(&$client, $response, &$message, &$interactions)
+ {
+ switch($this->state)
+ {
+ case SASL_CRAM_MD5_STATE_RESPOND_CHALLENGE:
+ $message=$this->credentials["user"]." ".$this->HMACMD5($this->credentials["password"], $response);
+ $this->state=SASL_CRAM_MD5_STATE_DONE;
+ break;
+ case SASL_CRAM_MD5_STATE_DONE:
+ $client->error="CRAM-MD5 authentication was finished without success";
+ return(SASL_FAIL);
+ default:
+ $client->error="invalid CRAM-MD5 authentication step state";
+ return(SASL_FAIL);
+ }
+ return(SASL_CONTINUE);
+ }
+};
+
+?> \ No newline at end of file
diff --git a/src/etc/inc/crypt.inc b/src/etc/inc/crypt.inc
new file mode 100644
index 0000000..8d96b26
--- /dev/null
+++ b/src/etc/inc/crypt.inc
@@ -0,0 +1,101 @@
+<?php
+
+/* $Id$ */
+/*
+ Copyright (C) 2008 Shrew Soft Inc
+ 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.
+
+ pfSense_BUILDER_BINARIES: /usr/bin/openssl
+ pfSense_MODULE: crypto
+
+*/
+
+ function crypt_data($val, $pass, $opt) {
+ $file = tempnam("/tmp", "php-encrypt");
+ file_put_contents("{$file}.dec", $val);
+ exec("/usr/bin/openssl enc {$opt} -aes-256-cbc -in {$file}.dec -out {$file}.enc -k " . escapeshellarg($pass));
+ if (file_exists("{$file}.enc")) {
+ $result = file_get_contents("{$file}.enc");
+ } else {
+ $result = "";
+ log_error("Failed to encrypt/decrypt data!");
+ }
+ @unlink($file);
+ @unlink("{$file}.dec");
+ @unlink("{$file}.enc");
+ return $result;
+ }
+
+ function encrypt_data(& $data, $pass) {
+ return base64_encode(crypt_data($data, $pass, "-e"));
+ }
+
+ function decrypt_data(& $data, $pass) {
+ return crypt_data(base64_decode($data), $pass, "-d");
+ }
+
+ function tagfile_reformat($in, & $out, $tag) {
+
+ $out = "---- BEGIN {$tag} ----\n";
+
+ $size = 80;
+ $oset = 0;
+ while ($size >= 64) {
+ $line = substr($in, $oset, 64);
+ $out .= $line."\n";
+ $size = strlen($line);
+ $oset += $size;
+ }
+
+ $out .= "---- END {$tag} ----\n";
+
+ return true;
+ }
+
+ function tagfile_deformat($in, & $out, $tag) {
+
+ $btag_val = "---- BEGIN {$tag} ----";
+ $etag_val = "---- END {$tag} ----";
+
+ $btag_len = strlen($btag_val);
+ $etag_len = strlen($etag_val);
+
+ $btag_pos = stripos($in, $btag_val);
+ $etag_pos = stripos($in, $etag_val);
+
+ if (($btag_pos === false) || ($etag_pos === false)) {
+ return false;
+ }
+
+ $body_pos = $btag_pos + $btag_len;
+ $body_len = strlen($in);
+ $body_len -= $btag_len;
+ $body_len -= $etag_len + 1;
+
+ $out = substr($in, $body_pos, $body_len);
+
+ return true;
+ }
+
+?>
diff --git a/src/etc/inc/digest_sasl_client.inc b/src/etc/inc/digest_sasl_client.inc
new file mode 100644
index 0000000..924887d
--- /dev/null
+++ b/src/etc/inc/digest_sasl_client.inc
@@ -0,0 +1,135 @@
+<?php
+/*
+ * digest_sasl_client.php
+ *
+ * @(#) $Id: digest_sasl_client.php,v 1.1 2005/10/27 05:24:15 mlemos Exp $
+ *
+ */
+
+define('SASL_DIGEST_STATE_START', 0);
+define('SASL_DIGEST_STATE_RESPOND_CHALLENGE', 1);
+define('SASL_DIGEST_STATE_DONE', 2);
+
+class digest_sasl_client_class
+{
+ var $credentials=array();
+ var $state=SASL_DIGEST_STATE_START;
+
+ Function unq($string)
+ {
+ return(($string[0]=='"' && $string[strlen($string)-1]=='"') ? substr($string, 1, strlen($string)-2) : $string);
+ }
+
+ Function H($data)
+ {
+ return md5($data);
+ }
+
+ Function KD($secret, $data)
+ {
+ return $this->H($secret.':'.$data);
+ }
+
+ Function Initialize(&$client)
+ {
+ return(1);
+ }
+
+ Function Start(&$client, &$message, &$interactions)
+ {
+ if($this->state!=SASL_DIGEST_STATE_START)
+ {
+ $client->error='Digest authentication state is not at the start';
+ return(SASL_FAIL);
+ }
+ $this->credentials=array(
+ 'user'=>'',
+ 'password'=>'',
+ 'uri'=>'',
+ 'method'=>'',
+ 'session'=>''
+ );
+ $defaults=array();
+ $status=$client->GetCredentials($this->credentials,$defaults,$interactions);
+ if($status==SASL_CONTINUE)
+ $this->state=SASL_DIGEST_STATE_RESPOND_CHALLENGE;
+ Unset($message);
+ return($status);
+ }
+
+ Function Step(&$client, $response, &$message, &$interactions)
+ {
+ switch($this->state)
+ {
+ case SASL_DIGEST_STATE_RESPOND_CHALLENGE:
+ $values=explode(',',$response);
+ $parameters=array();
+ for($v=0; $v<count($values); $v++)
+ $parameters[strtok(trim($values[$v]), '=')]=strtok('');
+
+ $message='username="'.$this->credentials['user'].'"';
+ if(!IsSet($parameters[$p='realm'])
+ && !IsSet($parameters[$p='nonce']))
+ {
+ $client->error='Digest authentication parameter '.$p.' is missing from the server response';
+ return(SASL_FAIL);
+ }
+ $message.=', realm='.$parameters['realm'];
+ $message.=', nonce='.$parameters['nonce'];
+ $message.=', uri="'.$this->credentials['uri'].'"';
+ if(IsSet($parameters['algorithm']))
+ {
+ $algorithm=$this->unq($parameters['algorithm']);
+ $message.=', algorithm='.$parameters['algorithm'];
+ }
+ else
+ $algorithm='';
+
+ $realm=$this->unq($parameters['realm']);
+ $nonce=$this->unq($parameters['nonce']);
+ if(IsSet($parameters['qop']))
+ {
+ switch($qop=$this->unq($parameters['qop']))
+ {
+ case "auth":
+ $cnonce=$this->credentials['session'];
+ break;
+ default:
+ $client->error='Digest authentication quality of protection '.$qop.' is not yet supported';
+ return(SASL_FAIL);
+ }
+ }
+ $nc_value='00000001';
+ if(IsSet($parameters['qop'])
+ && !strcmp($algorithm, 'MD5-sess'))
+ $A1=$this->H($this->credentials['user'].':'. $realm.':'. $this->credentials['password']).':'.$nonce.':'.$cnonce;
+ else
+ $A1=$this->credentials['user'].':'. $realm.':'. $this->credentials['password'];
+ $A2=$this->credentials['method'].':'.$this->credentials['uri'];
+ if(IsSet($parameters['qop']))
+ $response=$this->KD($this->H($A1), $nonce.':'. $nc_value.':'. $cnonce.':'. $qop.':'. $this->H($A2));
+ else
+ $response=$this->KD($this->H($A1), $nonce.':'. $this->H($A2));
+ $message.=', response="'.$response.'"';
+ if(IsSet($parameters['opaque']))
+ $message.=', opaque='.$parameters['opaque'];
+ if(IsSet($parameters['qop']))
+ $message.=', qop="'.$qop.'"';
+ $message.=', nc='.$nc_value;
+ if(IsSet($parameters['qop']))
+ $message.=', cnonce="'.$cnonce.'"';
+ $client->encode_response=0;
+ $this->state=SASL_DIGEST_STATE_DONE;
+ break;
+ case SASL_DIGEST_STATE_DONE:
+ $client->error='Digest authentication was finished without success';
+ return(SASL_FAIL);
+ default:
+ $client->error='invalid Digest authentication step state';
+ return(SASL_FAIL);
+ }
+ return(SASL_CONTINUE);
+ }
+};
+
+?> \ No newline at end of file
diff --git a/src/etc/inc/dot.hushlogin b/src/etc/inc/dot.hushlogin
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/etc/inc/dot.hushlogin
diff --git a/src/etc/inc/dyndns.class b/src/etc/inc/dyndns.class
new file mode 100644
index 0000000..cb21fb5
--- /dev/null
+++ b/src/etc/inc/dyndns.class
@@ -0,0 +1,1634 @@
+<?php
+ /*
+ * PHP.updateDNS (pfSense version)
+ *
+ * +====================================================+
+ * Services Supported:
+ * - DynDns (dyndns.org) [dynamic, static, custom]
+ * - DHSDns (dhs.org)
+ * - No-IP (no-ip.com)
+ * - EasyDNS (easydns.com)
+ * - DHS (www.dhs.org)
+ * - HN (hn.org) -- incomplete checking!
+ * - DynS (dyns.org)
+ * - ZoneEdit (zoneedit.com)
+ * - FreeDNS (freedns.afraid.org)
+ * - Loopia (loopia.se)
+ * - StaticCling (staticcling.org)
+ * - DNSexit (dnsexit.com)
+ * - OpenDNS (opendns.com)
+ * - Namecheap (namecheap.com)
+ * - HE.net (dns.he.net)
+ * - HE.net IPv6 (dns.he.net)
+ * - HE.net Tunnelbroker IP update (ipv4.tunnelbroker.net)
+ * - SelfHost (selfhost.de)
+ * - Amazon Route 53 (aws.amazon.com)
+ * - DNS-O-Matic (dnsomatic.com)
+ * - Custom DDNS (any URL)
+ * - Custom DDNS IPv6 (any URL)
+ * - CloudFlare (www.cloudflare.com)
+ * - Eurodns (eurodns.com)
+ * - GratisDNS (gratisdns.dk)
+ * - City Network (citynetwork.se)
+ * - GleSYS (glesys.com)
+ * - DNSimple (dnsimple.com)
+ * - Google Domains (domains.google.com)
+ * - DNS Made Easy (www.dnsmadeeasy.com)
+ * +----------------------------------------------------+
+ * Requirements:
+ * - PHP version 4.0.2 or higher with the CURL Library and the PCRE Library
+ * +----------------------------------------------------+
+ * Public Functions
+ * - updatedns()
+ *
+ * Private Functions
+ * - _update()
+ * - _checkStatus()
+ * - _error()
+ * - _detectChange()
+ * - _debug()
+ * - _checkIP()
+ * +----------------------------------------------------+
+ * DynDNS Dynamic - Last Tested: 12 July 2005
+ * DynDNS Static - Last Tested: NEVER
+ * DynDNS Custom - Last Tested: NEVER
+ * No-IP - Last Tested: 20 July 2008
+ * HN.org - Last Tested: 12 July 2005
+ * EasyDNS - Last Tested: 20 July 2008
+ * DHS - Last Tested: 12 July 2005
+ * ZoneEdit - Last Tested: NEVER
+ * Dyns - Last Tested: NEVER
+ * ODS - Last Tested: 02 August 2005
+ * FreeDNS - Last Tested: 23 Feb 2011
+ * Loopia - Last Tested: NEVER
+ * StaticCling - Last Tested: 27 April 2006
+ * DNSexit - Last Tested: 20 July 2008
+ * OpenDNS - Last Tested: 4 August 2008
+ * Namecheap - Last Tested: 31 August 2010
+ * HE.net - Last Tested: 7 July 2013
+ * HE.net IPv6 - Last Tested: 7 July 2013
+ * HE.net Tunnel - Last Tested: 28 June 2011
+ * SelfHost - Last Tested: 26 December 2011
+ * Amazon Route 53 - Last tested: 01 April 2012
+ * DNS-O-Matic - Last Tested: 9 September 2010
+ * CloudFlare - Last Tested: 30 May 2013
+ * Eurodns - Last Tested: 27 June 2013
+ * GratisDNS - Last Tested: 15 August 2012
+ * OVH DynHOST - Last Tested: NEVER
+ * City Network - Last Tested: 13 November 2013
+ * GleSYS - Last Tested: 3 February 2015
+ * DNSimple - Last Tested: 09 February 2015
+ * Google Domains - Last Tested: 27 April 2015
+ * DNS Made Easy - Last Tested: 27 April 2015
+ * +====================================================+
+ *
+ * @author E.Kristensen
+ * @link http://www.idylldesigns.com/projects/phpdns/
+ * @version 0.8
+ * @updated 13 October 05 at 21:02:42 GMT
+ *
+ * DNSexit/OpenDNS support and multiwan extension for pfSense by Ermal Luçi
+ * Custom DNS support by Matt Corallo
+ *
+ */
+
+ class updatedns {
+ var $_cacheFile;
+ var $_cacheFile_v6;
+ var $_debugFile;
+ var $_UserAgent = 'User-Agent: phpDynDNS/0.7';
+ var $_errorVerbosity = 0;
+ var $_dnsService;
+ var $_dnsUser;
+ var $_dnsPass;
+ var $_dnsHost;
+ var $_dnsIP;
+ var $_dnsWildcard;
+ var $_dnsMX;
+ var $_dnsBackMX;
+ var $_dnsServer;
+ var $_dnsPort;
+ var $_dnsUpdateURL;
+ var $_dnsZoneID;
+ var $_dnsTTL;
+ var $status;
+ var $_debugID;
+ var $_if;
+ var $_dnsResultMatch;
+ var $_dnsRequestIf;
+ var $_dnsRequestIfIP;
+ var $_dnsVerboseLog;
+ var $_curlIpresolveV4;
+ var $_curlSslVerifypeer;
+ var $_dnsMaxCacheAgeDays;
+ var $_dnsDummyUpdateDone;
+ var $_forceUpdateNeeded;
+ var $_useIPv6;
+
+ /*
+ * Public Constructor Function (added 12 July 05) [beta]
+ * - Gets the dice rolling for the update.
+ * - $dnsResultMatch should only be used with $dnsService = 'custom'
+ * - $dnsResultMatch is parsed for '%IP%', which is the IP the provider was updated to,
+ * - it is otherwise expected to be exactly identical to what is returned by the Provider.
+ * - $dnsUser, and $dnsPass indicate HTTP Auth for custom DNS, if they are needed in the URL (GET Variables), include them in $dnsUpdateURL.
+ * - $For custom requests, $dnsUpdateURL is parsed for '%IP%', which is replaced with the new IP.
+ */
+ function updatedns ($dnsService = '', $dnsHost = '', $dnsUser = '', $dnsPass = '',
+ $dnsWildcard = 'OFF', $dnsMX = '', $dnsIf = '', $dnsBackMX = '',
+ $dnsServer = '', $dnsPort = '', $dnsUpdateURL = '', $forceUpdate = false,
+ $dnsZoneID ='', $dnsTTL='', $dnsResultMatch = '', $dnsRequestIf = '',
+ $dnsID = '', $dnsVerboseLog = false, $curlIpresolveV4 = false, $curlSslVerifypeer = true) {
+
+ global $config, $g;
+
+ $this->_cacheFile = "{$g['conf_path']}/dyndns_{$dnsIf}{$dnsService}" . escapeshellarg($dnsHost) . "{$dnsID}.cache";
+ $this->_cacheFile_v6 = "{$g['conf_path']}/dyndns_{$dnsIf}{$dnsService}" . escapeshellarg($dnsHost) . "{$dnsID}_v6.cache";
+ $this->_debugFile = "{$g['varetc_path']}/dyndns_{$dnsIf}{$dnsService}" . escapeshellarg($dnsHost) . "{$dnsID}.debug";
+
+ $this->_curlIpresolveV4 = $curlIpresolveV4;
+ $this->_curlSslVerifypeer = $curlSslVerifypeer;
+ $this->_dnsVerboseLog = $dnsVerboseLog;
+ if ($this->_dnsVerboseLog) {
+ log_error("DynDns: updatedns() starting");
+ }
+
+ $dyndnslck = lock("DDNS".$dnsID, LOCK_EX);
+
+ if (!$dnsService) $this->_error(2);
+ switch ($dnsService) {
+ case 'freedns':
+ if (!$dnsHost) $this->_error(5);
+ break;
+ case 'namecheap':
+ if (!$dnsPass) $this->_error(4);
+ if (!$dnsHost) $this->_error(5);
+ break;
+ case 'route53':
+ if (!$dnsZoneID) $this->_error(8);
+ if (!$dnsTTL) $this->_error(9);
+ break;
+ case 'custom':
+ if (!$dnsUpdateURL) $this->_error(7);
+ break;
+ default:
+ if (!$dnsUser) $this->_error(3);
+ if (!$dnsPass) $this->_error(4);
+ if (!$dnsHost) $this->_error(5);
+ }
+
+ switch ($dnsService) {
+ case 'he-net-v6':
+ case 'custom-v6':
+ $this->_useIPv6 = true;
+ break;
+ default:
+ $this->_useIPv6 = false;
+ }
+ $this->_dnsService = strtolower($dnsService);
+ $this->_dnsUser = $dnsUser;
+ $this->_dnsPass = $dnsPass;
+ $this->_dnsHost = $dnsHost;
+ $this->_dnsServer = $dnsServer;
+ $this->_dnsPort = $dnsPort;
+ $this->_dnsWildcard = $dnsWildcard;
+ $this->_dnsMX = $dnsMX;
+ $this->_dnsZoneID = $dnsZoneID;
+ $this->_dnsTTL = $dnsTTL;
+ $this->_if = get_failover_interface($dnsIf);
+ $this->_checkIP();
+ $this->_dnsUpdateURL = $dnsUpdateURL;
+ $this->_dnsResultMatch = $dnsResultMatch;
+ $this->_dnsRequestIf = get_failover_interface($dnsRequestIf);
+ if ($this->_dnsVerboseLog) {
+ log_error("DynDNS ({$this->_dnsHost}): running get_failover_interface for {$dnsRequestIf}. found {$this->_dnsRequestIf}");
+ }
+ $this->_dnsRequestIfIP = get_interface_ip($dnsRequestIf);
+ $this->_dnsMaxCacheAgeDays = 25;
+ $this->_dnsDummyUpdateDone = false;
+ $this->_forceUpdateNeeded = $forceUpdate;
+
+ // Ensure that we were able to lookup the IP
+ if (!is_ipaddr($this->_dnsIP)) {
+ log_error("DynDNS ({$this->_dnsHost}) There was an error trying to determine the public IP for interface - {$dnsIf}({$this->_if}). Probably interface is not a WAN interface.");
+ unlock($dyndnslck);
+ return;
+ }
+
+ $this->_debugID = rand(1000000, 9999999);
+
+ if ($forceUpdate == false && $this->_detectChange() == false) {
+ $this->_error(10);
+ } else {
+ switch ($this->_dnsService) {
+ case 'glesys':
+ case 'dnsomatic':
+ case 'dyndns':
+ case 'dyndns-static':
+ case 'dyndns-custom':
+ case 'dhs':
+ case 'noip':
+ case 'noip-free':
+ case 'easydns':
+ case 'hn':
+ case 'zoneedit':
+ case 'dyns':
+ case 'ods':
+ case 'freedns':
+ case 'loopia':
+ case 'staticcling':
+ case 'dnsexit':
+ case 'custom':
+ case 'custom-v6':
+ case 'opendns':
+ case 'namecheap':
+ case 'he-net':
+ case 'he-net-v6':
+ case 'selfhost':
+ case 'he-net-tunnelbroker':
+ case 'route53':
+ case 'cloudflare':
+ case 'eurodns':
+ case 'gratisdns':
+ case 'ovh-dynhost':
+ case 'citynetwork':
+ case 'dnsimple':
+ case 'googledomains':
+ case 'dnsmadeeasy':
+ $this->_update();
+ if ($this->_dnsDummyUpdateDone == true) {
+ // If a dummy update was needed, then sleep a while and do the update again to put the proper address back.
+ // Some providers (e.g. No-IP free accounts) need to have at least 1 address change every month.
+ // If the address has not changed recently, or the user did "Force Update", then the code does
+ // a dummy address change for providers like this.
+ sleep(10);
+ $this->_update();
+ }
+ break;
+ default:
+ $this->_error(6);
+ break;
+ }
+ }
+
+ unlock($dyndnslck);
+ }
+
+ /*
+ * Private Function (added 12 July 05) [beta]
+ * Send Update To Selected Service.
+ */
+ function _update() {
+
+ if ($this->_dnsVerboseLog) {
+ log_error("DynDNS ({$this->_dnsHost}): DynDns _update() starting.");
+ }
+
+ if ($this->_dnsService != 'ods' and $this->_dnsService != 'route53 ') {
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_HEADER, 0);
+ curl_setopt($ch, CURLOPT_USERAGENT, $this->_UserAgent);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
+ curl_setopt($ch, CURLOPT_INTERFACE, 'if!' . $this->_dnsRequestIf);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 120); // Completely empirical
+ }
+
+ switch ($this->_dnsService) {
+ case 'glesys':
+ $needsIP = TRUE;
+ if ($this->_dnsVerboseLog) {
+ log_error("DynDNS: ({$this->_dnsHost}) DNS update() starting.");
+ }
+ $server = 'https://api.glesys.com/domain/updaterecord/format/json';
+ curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass);
+ $post_data['recordid'] = $this->_dnsHost;
+ $post_data['data'] = $this->_dnsIP;
+ curl_setopt($ch, CURLOPT_URL, $server);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
+ break;
+ case 'dyndns':
+ case 'dyndns-static':
+ case 'dyndns-custom':
+ $needsIP = FALSE;
+ if ($this->_dnsVerboseLog) {
+ log_error("DynDNS: ({$this->_dnsHost}) DNS update() starting.");
+ }
+ if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") {
+ $this->_dnsWildcard = "ON";
+ }
+ curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass);
+ $server = "https://members.dyndns.org/nic/update";
+ $port = "";
+ if ($this->_dnsServer) {
+ $server = $this->_dnsServer;
+ }
+ if ($this->_dnsPort) {
+ $port = ":" . $this->_dnsPort;
+ }
+ curl_setopt($ch, CURLOPT_URL, $server .$port . '?system=dyndns&hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard='.$this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=NO');
+ break;
+ case 'dhs':
+ $needsIP = TRUE;
+ $post_data['hostscmd'] = 'edit';
+ $post_data['hostscmdstage'] = '2';
+ $post_data['type'] = '4';
+ $post_data['updatetype'] = 'Online';
+ $post_data['mx'] = $this->_dnsMX;
+ $post_data['mx2'] = '';
+ $post_data['txt'] = '';
+ $post_data['offline_url'] = '';
+ $post_data['cloak'] = 'Y';
+ $post_data['cloak_title'] = '';
+ $post_data['ip'] = $this->_dnsIP;
+ $post_data['domain'] = 'dyn.dhs.org';
+ $post_data['hostname'] = $this->_dnsHost;
+ $post_data['submit'] = 'Update';
+ $server = "https://members.dhs.org/nic/hosts";
+ $port = "";
+ if ($this->_dnsServer) {
+ $server = $this->_dnsServer;
+ }
+ if ($this->_dnsPort) {
+ $port = ":" . $this->_dnsPort;
+ }
+ curl_setopt($ch, CURLOPT_URL, '{$server}{$port}');
+ curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
+ break;
+ case 'noip':
+ case 'noip-free':
+ $needsIP = TRUE;
+ $server = "https://dynupdate.no-ip.com/ducupdate.php";
+ $port = "";
+ if ($this->_dnsServer) {
+ $server = $this->_dnsServer;
+ }
+ if ($this->_dnsPort) {
+ $port = ":" . $this->_dnsPort;
+ }
+ if (($this->_dnsService == "noip-free") &&
+ ($this->_forceUpdateNeeded == true) &&
+ ($this->_dnsDummyUpdateDone == false)) {
+ // Update the IP to a dummy value to force No-IP free accounts to see a change.
+ $iptoset = "192.168.1.1";
+ $this->_dnsDummyUpdateDone = true;
+ log_error("DynDNS ({$this->_dnsHost}): Processing dummy update on No-IP free account. IP temporarily set to " . $iptoset);
+ } else {
+ $iptoset = $this->_dnsIP;
+ }
+ curl_setopt($ch, CURLOPT_URL, $server . $port . '?username=' . urlencode($this->_dnsUser) . '&pass=' . urlencode($this->_dnsPass) . '&hostname=' . $this->_dnsHost.'&ip=' . $iptoset);
+ break;
+ case 'easydns':
+ $needsIP = TRUE;
+ curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass);
+ $server = "https://members.easydns.com/dyn/dyndns.php";
+ $port = "";
+ if ($this->_dnsServer) {
+ $server = $this->_dnsServer;
+ }
+ if ($this->_dnsPort) {
+ $port = ":" . $this->_dnsPort;
+ }
+ curl_setopt($ch, CURLOPT_URL, $server . $port . '?hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard=' . $this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=' . $this->_dnsBackMX);
+ break;
+ case 'hn':
+ $needsIP = TRUE;
+ curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass);
+ $server = "http://dup.hn.org/vanity/update";
+ $port = "";
+ if ($this->_dnsServer) {
+ $server = $this->_dnsServer;
+ }
+ if ($this->_dnsPort) {
+ $port = ":" . $this->_dnsPort;
+ }
+ curl_setopt($ch, CURLOPT_URL, $server . $port . '?ver=1&IP=' . $this->_dnsIP);
+ break;
+ case 'zoneedit':
+ $needsIP = FALSE;
+ curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass);
+
+ $server = "https://dynamic.zoneedit.com/auth/dynamic.html";
+ $port = "";
+ if ($this->_dnsServer) {
+ $server = $this->_dnsServer;
+ }
+ if ($this->_dnsPort) {
+ $port = ":" . $this->_dnsPort;
+ }
+ curl_setopt($ch, CURLOPT_URL, "{$server}{$port}?host=" .$this->_dnsHost);
+ break;
+ case 'dyns':
+ $needsIP = FALSE;
+ $server = "https://www.dyns.cx/postscript011.php";
+ $port = "";
+ if ($this->_dnsServer) {
+ $server = $this->_dnsServer;
+ }
+ if ($this->_dnsPort) {
+ $port = ":" . $this->_dnsPort;
+ }
+ curl_setopt($ch, CURLOPT_URL, $server . $port . '?username=' . urlencode($this->_dnsUser) . '&password=' . $this->_dnsPass . '&host=' . $this->_dnsHost);
+ break;
+ case 'ods':
+ $needsIP = FALSE;
+ $misc_errno = 0;
+ $misc_error = "";
+ $server = "ods.org";
+ $port = "";
+ if ($this->_dnsServer) {
+ $server = $this->_dnsServer;
+ }
+ if ($this->_dnsPort) {
+ $port = ":" . $this->_dnsPort;
+ }
+ $this->con['socket'] = fsockopen("{$server}{$port}", "7070", $misc_errno, $misc_error, 30);
+ /* Check that we have connected */
+ if (!$this->con['socket']) {
+ print "error! could not connect.";
+ break;
+ }
+ /* Here is the loop. Read the incoming data (from the socket connection) */
+ while (!feof($this->con['socket'])) {
+ $this->con['buffer']['all'] = trim(fgets($this->con['socket'], 4096));
+ $code = substr($this->con['buffer']['all'], 0, 3);
+ sleep(1);
+ switch ($code) {
+ case 100:
+ fputs($this->con['socket'], "LOGIN ".$this->_dnsUser." ".$this->_dnsPass."\n");
+ break;
+ case 225:
+ fputs($this->con['socket'], "DELRR ".$this->_dnsHost." A\n");
+ break;
+ case 901:
+ fputs($this->con['socket'], "ADDRR ".$this->_dnsHost." A ".$this->_dnsIP."\n");
+ break;
+ case 795:
+ fputs($this->con['socket'], "QUIT\n");
+ break;
+ }
+ }
+ $this->_checkStatus(0, $code);
+ break;
+ case 'freedns':
+ $needIP = FALSE;
+ curl_setopt($ch, CURLOPT_URL, 'https://freedns.afraid.org/dynamic/update.php?' . $this->_dnsPass);
+ break;
+ case 'dnsexit':
+ $needsIP = TRUE;
+ curl_setopt($ch, CURLOPT_URL, 'https://www.dnsexit.com/RemoteUpdate.sv?login='.$this->_dnsUser. '&password='.$this->_dnsPass.'&host='.$this->_dnsHost.'&myip='.$this->_dnsIP);
+ break;
+ case 'loopia':
+ $needsIP = TRUE;
+ curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass);
+ curl_setopt($ch, CURLOPT_URL, 'https://dns.loopia.se/XDynDNSServer/XDynDNS.php?hostname='.$this->_dnsHost.'&myip='.$this->_dnsIP);
+ break;
+ case 'opendns':
+ $needsIP = FALSE;
+ if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") $this->_dnsWildcard = "ON";
+ curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass);
+ $server = "https://updates.opendns.com/nic/update?hostname=". $this->_dnsHost;
+ $port = "";
+ if ($this->_dnsServer) {
+ $server = $this->_dnsServer;
+ }
+ if ($this->_dnsPort) {
+ $port = ":" . $this->_dnsPort;
+ }
+ curl_setopt($ch, CURLOPT_URL, $server .$port);
+ break;
+
+ case 'staticcling':
+ $needsIP = FALSE;
+ curl_setopt($ch, CURLOPT_URL, 'https://www.staticcling.org/update.html?login='.$this->_dnsUser.'&pass='.$this->_dnsPass);
+ break;
+ case 'dnsomatic':
+ /* Example syntax
+ https://username:password@updates.dnsomatic.com/nic/update?hostname=yourhostname&myip=ipaddress&wildcard=NOCHG&mx=NOCHG&backmx=NOCHG
+ */
+ $needsIP = FALSE;
+ if ($this->_dnsVerboseLog) {
+ log_error("DNS-O-Matic: DNS update() starting.");
+ }
+ if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") {
+ $this->_dnsWildcard = "ON";
+ }
+ /*
+ Reference: https://www.dnsomatic.com/wiki/api
+ DNS-O-Matic usernames are 3-25 characters.
+ DNS-O-Matic passwords are 6-20 characters.
+ All ASCII letters and numbers accepted.
+ Dots, dashes, and underscores allowed, but not at the beginning or end of the string.
+ Required: "rawurlencode" http://www.php.net/manual/en/function.rawurlencode.php
+ Encodes the given string according to RFC 3986.
+ */
+ $server = "https://" . rawurlencode($this->_dnsUser) . ":" . rawurlencode($this->_dnsPass) . "@updates.dnsomatic.com/nic/update?hostname=";
+ if ($this->_dnsServer) {
+ $server = $this->_dnsServer;
+ }
+ if ($this->_dnsPort) {
+ $port = ":" . $this->_dnsPort;
+ }
+ curl_setopt($ch, CURLOPT_URL, $server . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard='.$this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=NOCHG');
+ break;
+ case 'namecheap':
+ /* Example:
+ https://dynamicdns.park-your-domain.com/update?host=[host_name]&domain=[domain.com]&password=[domain_password]&ip=[your_ip]
+ */
+ $needsIP = FALSE;
+ if ($this->_dnsVerboseLog) {
+ log_error("Namecheap ({$this->_dnsHost}): DNS update() starting.");
+ }
+ $dparts = explode(".", trim($this->_dnsHost));
+ $domain_part_count = ($dparts[count($dparts)-1] == "uk") ? 3 : 2;
+ $domain_offset = count($dparts) - $domain_part_count;
+ $hostname = implode(".", array_slice($dparts, 0, $domain_offset));
+ $domain = implode(".", array_slice($dparts, $domain_offset));
+ $dnspass = trim($this->_dnsPass);
+ $server = "https://dynamicdns.park-your-domain.com/update?host={$hostname}&domain={$domain}&password={$dnspass}&ip={$this->_dnsIP}";
+ curl_setopt($ch, CURLOPT_URL, $server);
+ break;
+ case 'he-net':
+ case 'he-net-v6':
+ $needsIP = FALSE;
+ if ($this->_dnsVerboseLog) {
+ log_error("HE.net ({$this->_dnsHost}): DNS update() starting.");
+ }
+ $server = "https://dyn.dns.he.net/nic/update?";
+ curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
+ curl_setopt($ch, CURLOPT_URL, $server . 'hostname=' . $this->_dnsHost . '&password=' . $this->_dnsPass . '&myip=' . $this->_dnsIP);
+ break;
+ case 'he-net-tunnelbroker':
+ $needsIP = FALSE;
+ if ($this->_dnsVerboseLog) {
+ log_error("HE.net Tunnelbroker: DNS update() starting.");
+ }
+ $server = "https://ipv4.tunnelbroker.net/ipv4_end.php?";
+ curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
+ curl_setopt($ch, CURLOPT_URL, $server . 'tid=' . $this->_dnsHost);
+ break;
+ case 'selfhost':
+ $needsIP = FALSE;
+ if ($this->_dnsVerboseLog) {
+ log_error("SelfHost: DNS update() starting.");
+ }
+ if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") {
+ $this->_dnsWildcard = "ON";
+ }
+ curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass);
+ $server = "https://carol.selfhost.de/nic/update";
+ $port = "";
+ if ($this->_dnsServer) {
+ $server = $this->_dnsServer;
+ }
+ if ($this->_dnsPort) {
+ $port = ":" . $this->_dnsPort;
+ }
+ curl_setopt($ch, CURLOPT_URL, $server .$port . '?system=dyndns&hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard='.$this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=NO');
+ break;
+ case 'route53':
+ if ($this->_dnsVerboseLog) {
+ log_error("Route53 ({$this->_dnsHost}): DNS update() starting.");
+ }
+
+ /* Setting Variables */
+ $hostname = "{$this->_dnsHost}.";
+ $ZoneID = $this->_dnsZoneID;
+ $AccessKeyId = $this->_dnsUser;
+ $SecretAccessKey = $this->_dnsPass;
+ $NewIP = $this->_dnsIP;
+ $NewTTL = $this->_dnsTTL;
+
+ /* Include Route 53 Library Class */
+ require_once('/etc/inc/r53.class');
+
+ /* Set Amazon AWS Credentials for this record */
+ $r53 = new Route53($AccessKeyId, $SecretAccessKey);
+
+ /* Function to find old values of records in Route 53 */
+ if (!function_exists('Searchrecords')) {
+ function SearchRecords($records, $name) {
+ $result = array();
+ foreach ($records as $record) {
+ if (strtolower($record['Name']) == strtolower($name)) {
+ $result [] = $record;
+ }
+ }
+ return ($result) ? $result : false;
+ }
+ }
+
+ $records = $r53->listResourceRecordSets("/hostedzone/$ZoneID");
+
+ /* Get IP for your hostname in Route 53 */
+ if (false !== ($a_result = SearchRecords($records['ResourceRecordSets'], "$hostname"))) {
+ $OldTTL = $a_result[0][TTL];
+ $OldIP = $a_result[0][ResourceRecords][0];
+ } else {
+ $OldIP = "";
+ }
+
+ /* Check if we need to update DNS Record */
+ if ($OldIP !== $NewIP) {
+ if (!empty($OldIP)) {
+ /* Your Hostname already exists, deleting and creating it again */
+ $changes = array();
+ $changes[] = $r53->prepareChange(DELETE, $hostname, A, $OldTTL, $OldIP);
+ $changes[] = $r53->prepareChange(CREATE, $hostname, A, $NewTTL, $NewIP);
+ $result = $r53->changeResourceRecordSets("/hostedzone/$ZoneID", $changes);
+ } else {
+ /* Your Hostname does not exist yet, creating it */
+ $changes = $r53->prepareChange(CREATE, $hostname, A, $NewTTL, $NewIP);
+ $result = $r53->changeResourceRecordSets("/hostedzone/$ZoneID", $changes);
+ }
+ }
+ $this->_checkStatus(0, $result);
+ break;
+ case 'custom':
+ case 'custom-v6':
+ if ($this->_dnsVerboseLog) {
+ log_error("Custom DDNS ({$this->_dnsHost}): DNS update() starting.");
+ }
+ if (strstr($this->dnsUpdateURL, "%IP%")) {$needsIP = TRUE;} else {$needsIP = FALSE;}
+ if ($this->_dnsUser != '') {
+ if ($this->_curlIpresolveV4) {
+ curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
+ }
+ if ($this->_curlSslVerifypeer) {
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, TRUE);
+ } else {
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
+ }
+ curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
+ curl_setopt($ch, CURLOPT_USERPWD, "{$this->_dnsUser}:{$this->_dnsPass}");
+ }
+ $server = str_replace("%IP%", $this->_dnsIP, $this->_dnsUpdateURL);
+ if ($this->_dnsVerboseLog) {
+ log_error("Sending request to: ".$server);
+ }
+ curl_setopt($ch, CURLOPT_URL, $server);
+ break;
+ case 'cloudflare':
+ $needsIP = TRUE;
+ $dnsServer ='www.cloudflare.com';
+ $dnsHost = str_replace(' ', '', $this->_dnsHost);
+ $URL = "https://{$dnsServer}/api.html?a=DIUP&email={$this->_dnsUser}&tkn={$this->_dnsPass}&ip={$this->_dnsIP}&hosts={$dnsHost}";
+ curl_setopt($ch, CURLOPT_URL, $URL);
+ break;
+ case 'eurodns':
+ $needsIP = TRUE;
+ if ($this->_dnsVerboseLog) {
+ log_error("EuroDynDns ({$this->_dnsHost}) DNS update() starting.");
+ }
+ curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass);
+ $server = "https://update.eurodyndns.org/update/";
+ $port = "";
+ if ($this->_dnsPort) {
+ $port = ":" . $this->_dnsPort;
+ }
+ curl_setopt($ch, CURLOPT_URL, $server .$port . '?hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP);
+ break;
+ case 'gratisdns':
+ $needsIP = TRUE;
+ if ($this->_dnsVerboseLog) {
+ log_error("GratisDNS.dk ({$this->_dnsHost}): DNS update() starting.");
+ }
+ $server = "https://ssl.gratisdns.dk/ddns.phtml";
+ list($hostname, $domain) = explode(".", $this->_dnsHost, 2);
+ curl_setopt($ch, CURLOPT_URL, $server . '?u=' . $this->_dnsUser . '&p=' . $this->_dnsPass . '&h=' . $this->_dnsHost . '&d=' . $domain . '&i=' . $this->_dnsIP);
+ break;
+ case 'ovh-dynhost':
+ $needsIP = FALSE;
+ if ($this->_dnsVerboseLog) {
+ log_error("OVH DynHOST: ({$this->_dnsHost}) DNS update() starting.");
+ }
+ if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") $this->_dnsWildcard = "ON";
+ curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass);
+ $server = "https://www.ovh.com/nic/update";
+ $port = "";
+ if ($this->_dnsServer) {
+ $server = $this->_dnsServer;
+ }
+ if ($this->_dnsPort) {
+ $port = ":" . $this->_dnsPort;
+ }
+ curl_setopt($ch, CURLOPT_URL, $server .$port . '?system=dyndns&hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard='.$this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=NO');
+ break;
+ case 'citynetwork':
+ $needsIP = TRUE;
+ if ($this->_dnsVerboseLog) {
+ log_error("City Network: ({$this->_dnsHost}) DNS update() starting.");
+ }
+ curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass);
+ $server = 'https://dyndns.citynetwork.se/nic/update';
+ $port = "";
+ if ($this->_dnsServer) {
+ $server = $this->_dnsServer;
+ }
+ if ($this->_dnsPort) {
+ $port = ":" . $this->_dnsPort;
+ }
+ curl_setopt($ch, CURLOPT_URL, $server .$port . '?hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP);
+ break;
+ case 'dnsimple':
+ /* Uses DNSimple's REST API
+ Requires username and Account API token passed in header
+ Piggybacks on Route 53's ZoneID field for DNSimple record ID
+ Data sent as JSON */
+ $needsIP = TRUE;
+ $server = 'https://api.dnsimple.com/v1/domains/';
+ $token = $this->_dnsUser . ':' . $this->_dnsPass;
+ $jsondata = '{"record":{"content":"' . $this->_dnsIP . '","ttl":"' . $this->_dnsTTL . '"}}';
+ curl_setopt($ch, CURLOPT_HEADER, 1);
+ curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
+ curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: application/json', 'Content-Type: application/json', 'X-DNSimple-Token: ' . $token));
+ curl_setopt($ch, CURLOPT_URL, $server . $this->_dnsHost . '/records/' . $this->_dnsZoneID);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $jsondata);
+ break;
+ case 'googledomains':
+ $needsIP = FALSE;
+ if ($this->_dnsVerboseLog) {
+ log_error("Google Domains: ({$this->_dnsHost}) DNS update() starting.");
+ }
+ $post_data['username:password'] = $this->_dnsUser . ':' . $this->_dnsPass;
+ $post_data['hostname'] = $this->_dnsHost;
+ $post_data['myip'] = $this->_dnsIP;
+ $post_data['offline'] = 'no';
+ $server = "https://domains.google.com/nic/update";
+ $port = "";
+ curl_setopt($ch, CURLOPT_URL, 'https://domains.google.com/nic/update');
+ curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser.':'.$this->_dnsPass);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
+ break;
+ case 'dnsmadeeasy':
+ $needsIP = TRUE;
+ if ($this->_dnsVerboseLog) {
+ log_error("DNS Made Easy ({$this->_dnsHost}): DNS update() starting.");
+ }
+ $server = "https://cp.dnsmadeeasy.com/servlet/updateip";
+ curl_setopt($ch, CURLOPT_URL, $server . '?username=' . $this->_dnsUser . '&password=' . $this->_dnsPass . '&id=' . $this->_dnsHost . '&ip=' . $this->_dnsIP);
+ break;
+ default:
+ break;
+ }
+ if ($this->_dnsService != 'ods' and $this->_dnsService != 'route53') {
+ $data = curl_exec($ch);
+ $this->_checkStatus($ch, $data);
+ @curl_close($ch);
+ }
+ }
+
+ /*
+ * Private Function (added 12 July 2005) [beta]
+ * Retrieve Update Status
+ */
+ function _checkStatus($ch, $data) {
+ if ($this->_dnsVerboseLog) {
+ log_error("DynDNS ({$this->_dnsHost}): DynDns _checkStatus() starting.");
+ log_error("DynDNS ({$this->_dnsHost}): Current Service: {$this->_dnsService}");
+ }
+ $successful_update = false;
+ if ($this->_dnsService != 'ods' and $this->_dnsService != 'route53' && @curl_error($ch)) {
+ $status = "Curl error occurred: " . curl_error($ch);
+ log_error($status);
+ $this->status = $status;
+ return;
+ }
+ switch ($this->_dnsService) {
+ case 'glesys':
+ if (preg_match('/Record updated/i', $data)) {
+ $status = "GleSYS ({$this->_dnsHost}): (Success) IP Address Changed Successfully! (" . $this->_dnsIP . ")";
+ $successful_update = true;
+ } else {
+ $status = "GleSYS ({$this->_dnsHost}): (Unknown Response)";
+ log_error("GleSYS ({$this->_dnsHost}): PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'dnsomatic':
+ if (preg_match('/badauth/i', $data)) {
+ $status = "DNS-O-Matic ({$this->_dnsHost}): The DNS-O-Matic username or password specified are incorrect. No updates will be distributed to services until this is resolved.";
+ } else if (preg_match('/notfqdn /i', $data)) {
+ $status = "DNS-O-Matic ({$this->_dnsHost}): The hostname specified is not a fully-qualified domain name. If no hostnames included, notfqdn will be returned once.";
+ } else if (preg_match('/nohost/i', $data)) {
+ $status = "DNS-O-Matic ({$this->_dnsHost}): The hostname passed could not be matched to any services configured. The service field will be blank in the return code.";
+ } else if (preg_match('/numhost/i', $data)) {
+ $status = "DNS-O-Matic ({$this->_dnsHost}): You may update up to 20 hosts. numhost is returned if you try to update more than 20 or update a round-robin.";
+ } else if (preg_match('/abuse/i', $data)) {
+ $status = "DNS-O-Matic ({$this->_dnsHost}): The hostname is blocked for update abuse.";
+ } else if (preg_match('/good/i', $data)) {
+ $status = "DNS-O-Matic ({$this->_dnsHost}): (Success) IP Address Changed Successfully! (" . $this->_dnsIP . ")";
+ $successful_update = true;
+ } else if (preg_match('/dnserr/i', $data)) {
+ $status = "DNS-O-Matic ({$this->_dnsHost}): DNS error encountered. Stop updating for 30 minutes.";
+ } else {
+ $status = "DNS-O-Matic ({$this->_dnsHost}): (Unknown Response)";
+ log_error("DNS-O-Matic ({$this->_dnsHost}): PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'citynetwork':
+ if (preg_match('/notfqdn/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Not A FQDN!";
+ } else if (preg_match('/nohost/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) No such host";
+ } else if (preg_match('/nochg/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) No Change In IP Address";
+ $successful_update = true;
+ } else if (preg_match('/good/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully! (" . $this->_dnsIP . ")";
+ $successful_update = true;
+ } else if (preg_match('/badauth/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) User Authorization Failed";
+ } else {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)";
+ log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'ovh-dynhost':
+ case 'dyndns':
+ if (preg_match('/notfqdn/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Not A FQDN!";
+ } else if (preg_match('/nochg/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) No Change In IP Address";
+ $successful_update = true;
+ } else if (preg_match('/good/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully! (" . $this->_dnsIP . ")";
+ $successful_update = true;
+ } else if (preg_match('/noauth/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) User Authorization Failed";
+ } else {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)";
+ log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'dyndns-static':
+ if (preg_match('/notfqdn/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Not A FQDN!";
+ } else if (preg_match('/nochg/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) No Change In IP Address";
+ $successful_update = true;
+ } else if (preg_match('/good/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully!";
+ $successful_update = true;
+ } else if (preg_match('/noauth/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) User Authorization Failed";
+ } else {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)";
+ log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'dyndns-custom':
+ if (preg_match('/notfqdn/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Not A FQDN!";
+ } else if (preg_match('/nochg/i', $data)) {
+ $status = "phpDynDNS: (Success) No Change In IP Address";
+ $successful_update = true;
+ } else if (preg_match('/good/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully!";
+ $successful_update = true;
+ } else if (preg_match('/noauth/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) User Authorization Failed";
+ } else {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)";
+ log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'dhs':
+ break;
+ case 'noip':
+ case 'noip-free':
+ list($ip, $code) = explode(":", $data);
+ switch ($code) {
+ case 0:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP address is current, no update performed.";
+ $successful_update = true;
+ break;
+ case 1:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) DNS hostname update successful.";
+ $successful_update = true;
+ break;
+ case 2:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Hostname supplied does not exist.";
+ break;
+ case 3:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Invalid Username.";
+ break;
+ case 4:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Invalid Password.";
+ break;
+ case 5:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) To many updates sent.";
+ break;
+ case 6:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Account disabled due to violation of No-IP terms of service.";
+ break;
+ case 7:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Invalid IP. IP Address submitted is improperly formatted or is a private IP address or is on a blacklist.";
+ break;
+ case 8:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Disabled / Locked Hostname.";
+ break;
+ case 9:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Host updated is configured as a web redirect and no update was performed.";
+ break;
+ case 10:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Group supplied does not exist.";
+ break;
+ case 11:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) DNS group update is successful.";
+ $successful_update = true;
+ break;
+ case 12:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) DNS group is current, no update performed.";
+ $successful_update = true;
+ break;
+ case 13:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Update client support not available for supplied hostname or group.";
+ break;
+ case 14:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Hostname supplied does not have offline settings configured.";
+ break;
+ case 99:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Client disabled. Client should exit and not perform any more updates without user intervention.";
+ break;
+ case 100:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Client disabled. Client should exit and not perform any more updates without user intervention.";
+ break;
+ default:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)";
+ $this->_debug("Unknown Response: ".$data);
+ break;
+ }
+ break;
+ case 'easydns':
+ if (preg_match('/NOACCESS/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Authentication Failed: Username and/or Password was Incorrect.";
+ } else if (preg_match('/NOSERVICE/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) No Service: Dynamic DNS Service has been disabled for this domain.";
+ } else if (preg_match('/ILLEGAL INPUT/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Illegal Input: Self-Explanatory";
+ } else if (preg_match('/TOOSOON/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Too Soon: Not Enough Time Has Elapsed Since Last Update";
+ } else if (preg_match('/NOERROR/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Updated Successfully!";
+ $successful_update = true;
+ } else {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)";
+ log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'hn':
+ /* FIXME: add checks */
+ break;
+ case 'zoneedit':
+ if (preg_match('/799/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error 799) Update Failed!";
+ } else if (preg_match('/700/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error 700) Update Failed!";
+ } else if (preg_match('/200/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Updated Successfully!";
+ $successful_update = true;
+ } else if (preg_match('/201/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Updated Successfully!";
+ $successful_update = true;
+ } else {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)";
+ log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'dyns':
+ if (preg_match("/400/i", $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Bad Request - The URL was malformed. Required parameters were not provided.";
+ } else if (preg_match('/402/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Update Too Soon - You have tried updating to quickly since last change.";
+ } else if (preg_match('/403/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Database Error - There was a server-sided database error.";
+ } else if (preg_match('/405/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Hostname Error - The hostname (".$this->_dnsHost.") doesn't belong to you.";
+ } else if (preg_match('/200/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Updated Successfully!";
+ $successful_update = true;
+ } else {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)";
+ log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'ods':
+ if (preg_match("/299/i", $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Updated Successfully!";
+ $successful_update = true;
+ } else {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)";
+ log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'freedns':
+ if (preg_match("/has not changed./i", $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) No Change In IP Address";
+ $successful_update = true;
+ } else if (preg_match("/Updated/i", $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully!";
+ $successful_update = true;
+ } else {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)";
+ log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'dnsexit':
+ if (preg_match("/is the same/i", $data)) {
+ $status = "phpDynDns ({$this->_dnsHost}): (Success) No Change In IP Address";
+ $successful_update = true;
+ } else if (preg_match("/Success/i", $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully!";
+ $successful_update = true;
+ } else {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)";
+ log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'loopia':
+ if (preg_match("/nochg/i", $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) No Change In IP Address";
+ $successful_update = true;
+ } else if (preg_match("/good/i", $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully!";
+ $successful_update = true;
+ } else if (preg_match('/badauth/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) User Authorization Failed";
+ } else {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)";
+ log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'opendns':
+ if (preg_match('/badauth/i', $data)) {
+ $status = "phpDynDNS({$this->_dnsHost}): (Error) Not a valid username or password!";
+ } else if (preg_match('/nohost/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Hostname you are trying to update does not exist.";
+ $successful_update = true;
+ } else if (preg_match('/good/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully! (" . $this->_dnsIP . ")";
+ $successful_update = true;
+ } else if (preg_match('/yours/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) hostname specified exists, but not under the username specified.";
+ } else if (preg_match('/abuse/i', $data)) {
+ $status = "phpDynDns ({$this->_dnsHost}): (Error) Updating too frequently, considered abuse.";
+ } else {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)";
+ log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'staticcling':
+ if (preg_match("/invalid ip/i", $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Bad Request - The IP provided was invalid.";
+ } else if (preg_match('/required info missing/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Bad Request - Required parameters were not provided.";
+ } else if (preg_match('/invalid characters/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Bad Request - Illegal characters in either the username or the password.";
+ } else if (preg_match('/bad password/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Invalid password.";
+ } else if (preg_match('/account locked/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) This account has been administratively locked.";
+ } else if (preg_match('/update too frequent/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Updating too frequently.";
+ } else if (preg_match('/DB error/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Server side error.";
+ } else if (preg_match('/success/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Updated Successfully!";
+ $successful_update = true;
+ } else {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)";
+ log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'namecheap':
+ $tmp = str_replace("^M", "", $data);
+ $ncresponse = @xml2array($tmp);
+ if (preg_match("/internal server error/i", $data)) {
+ $status = "phpDynDNS: (Error) Server side error.";
+ } else if (preg_match("/request is badly formed/i", $data)) {
+ $status = "phpDynDNS: (Error) Badly Formed Request (check your settings).";
+ } else if ($ncresponse['interface-response']['ErrCount'] === "0") {
+ $status = "phpDynDNS: (Success) IP Address Updated Successfully!";
+ $successful_update = true;
+ } else if (is_numeric($ncresponse['interface-response']['ErrCount']) && ($ncresponse['interface-response']['ErrCount'] > 0)) {
+ $status = "phpDynDNS: (Error) " . implode(", ", $ncresponse["interface-response"]["errors"]);
+ $successful_update = true;
+ } else {
+ $status = "phpDynDNS: (Unknown Response)";
+ log_error("phpDynDNS: PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+
+ case 'he-net':
+ case 'he-net-v6':
+ if (preg_match("/badip/i", $data)) {
+ $status = "phpDynDNS: (Error) Bad Request - The IP provided was invalid.";
+ } else if (preg_match('/nohost/i', $data)) {
+ $status = "phpDynDNS: (Error) Bad Request - A hostname was not provided.";
+ } else if (preg_match('/badauth/i', $data)) {
+ $status = "phpDynDNS: (Error) Invalid username or password.";
+ } else if (preg_match('/good/i', $data)) {
+ $status = "phpDynDNS: (Success) IP Address Updated Successfully!";
+ $successful_update = true;
+ } else if (preg_match('/nochg/i', $data)) {
+ $status = "phpDynDNS: (Success) No Change In IP Address.";
+ $successful_update = true;
+ } else {
+ $status = "phpDynDNS: (Unknown Response)";
+ log_error("phpDynDNS: PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'he-net-tunnelbroker':
+ /*
+ -ERROR: Missing parameter(s).
+ -ERROR: Invalid API key or password
+ -ERROR: Tunnel not found
+ -ERROR: Another tunnel exists for this IP.
+ -ERROR: This tunnel is already associated with this IP address
+ +OK: Tunnel endpoint updated to: x.x.x.x
+ */
+ if (preg_match("/Missing parameter/i", $data)) {
+ $status = "phpDynDNS: (Error) Bad Request - Missing/Invalid Parameters.";
+ } else if (preg_match('/Tunnel not found/i', $data)) {
+ $status = "phpDynDNS: (Error) Bad Request - Invalid Tunnel ID.";
+ } else if (preg_match('/Invalid API key or password/i', $data)) {
+ $status = "phpDynDNS: (Error) Invalid username or password.";
+ } else if (preg_match('/OK:/i', $data)) {
+ $status = "phpDynDNS: (Success) IP Address Updated Successfully!";
+ $successful_update = true;
+ } else if (preg_match('/This tunnel is already associated with this IP address/i', $data)) {
+ $status = "phpDynDNS: (Success) No Change In IP Address.";
+ $successful_update = true;
+ } else {
+ $status = "phpDynDNS: (Unknown Response)";
+ log_error("phpDynDNS: PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'selfhost':
+ if (preg_match('/notfqdn/i', $data)) {
+ $status = "phpDynDNS: (Error) Not A FQDN!";
+ } else if (preg_match('/nochg/i', $data)) {
+ $status = "phpDynDNS: (Success) No Change In IP Address";
+ $successful_update = true;
+ } else if (preg_match('/good/i', $data)) {
+ $status = "phpDynDNS: (Success) IP Address Changed Successfully! (" . $this->_dnsIP . ")";
+ $successful_update = true;
+ } else if (preg_match('/noauth/i', $data)) {
+ $status = "phpDynDNS: (Error) User Authorization Failed";
+ } else {
+ $status = "phpDynDNS: (Unknown Response)";
+ log_error("phpDynDNS: PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'route53':
+ $successful_update = true;
+ break;
+ case 'custom':
+ case 'custom-v6':
+ $successful_update = false;
+ if ($this->_dnsResultMatch == "") {
+ $successful_update = true;
+ } else {
+ $this->_dnsResultMatch = str_replace("%IP%", $this->_dnsIP, $this->_dnsResultMatch);
+ $matches = preg_split("/(?<!\\\\)\\|/", $this->_dnsResultMatch);
+ foreach ($matches as $match) {
+ $match= str_replace("\\|", "|", $match);
+ if (strcmp($match, trim($data, "\t\n\r")) == 0) {
+ $successful_update = true;
+ }
+ }
+ unset ($matches);
+ }
+ if ($successful_update == true) {
+ $status = "phpDynDNS: (Success) IP Address Updated Successfully!";
+ } else {
+ $status = "phpDynDNS: (Error) Result did not match.";
+ }
+ break;
+ case 'cloudflare':
+ // receive multiple results
+ $data = explode("\n", $data);
+ $lines = count($data)-1;
+
+ // loop over the lines
+ for ($pos = 0; ($successful_update || $pos == 0) && $pos < $lines; $pos++) {
+ $resp = $data[$pos];
+ if (preg_match('/UAUTH/i', $resp)) {
+ $status = "DynDNS: The username specified is not authorized to update this hostname and domain.";
+ } else if (preg_match('/NOHOST/i', $resp)) {
+ $status = "DynDNS: No valid FQDN (fully qualified domain name) was specified";
+ } else if (preg_match('/INVLDHST/i', $resp)) {
+ $status = "DynDNS: An invalid hostname was specified. This may be due to the fact the hostname has not been created in the system. Creating new host names via clients is not supported.";
+ } else if (preg_match('/INVLDIP/i', $resp)) {
+ $status = "DynDNS: The IP address given is not valid.";
+ } else if (preg_match('/DUPHST/i', $resp)) {
+ $status = "DynDNS: Duplicate values exist for a record. Only single values for records are supported currently.";
+ } else if (preg_match('/NOUPDATE/i', $resp)) {
+ $status = "DynDNS: No changes made to the hostname (" . strtok($resp, ' ') . "). Continual updates with no changes lead to blocked clients.";
+ $successful_update = true; //success if it is the same so that it saves
+ } else if (preg_match('/OK/i', $resp)) {
+ $status = "DynDNS: (Success) (" . strtok($resp, ' ') . ") IP Address for Changed Successfully!";
+ $successful_update = true;
+ } else {
+ $status = "DynDNS: (Unknown Response)";
+ log_error("DynDNS: PAYLOAD: {$resp}");
+ $this->_debug($resp);
+ }
+ log_error($status);
+ }
+ break;
+ case 'eurodns':
+ if (preg_match('/notfqdn/i', $data)) {
+ $status = "phpDynDNS: (Error) Not A FQDN!";
+ } else if (preg_match('/nochg/i', $data)) {
+ $status = "phpDynDNS: (Success) No Change In IP Address";
+ $successful_update = true;
+ } else if (preg_match('/good/i', $data)) {
+ $status = "phpDynDNS: (Success) IP Address Changed Successfully! (" . $this->_dnsIP . ")";
+ $successful_update = true;
+ } else if (preg_match('/badauth/i', $data)) {
+ $status = "phpDynDNS: (Error) User Authorization Failed";
+ } else {
+ $status = "phpDynDNS: (Unknown Response)";
+ log_error("phpDynDNS: PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'gratisdns':
+ if (preg_match('/Forkerte værdier/i', $data)) {
+ $status = "phpDynDNS: (Error) Wrong values - Update could not be completed.";
+ } else if (preg_match('/Bruger login: Bruger eksistere ikke/i', $data)) {
+ $status = "phpDynDNS: (Error) Unknown username - User does not exist.";
+ } else if (preg_match('/Bruger login: 1Fejl i kodeord/i', $data)) {
+ $status = "phpDynDNS: (Error) Wrong password - Remember password is case sensitive.";
+ } else if (preg_match('/Domæne kan IKKE administreres af bruger/i', $data)) {
+ $status = "phpDynDNS: (Error) User unable to administer the selected domain.";
+ } else if (preg_match('/OK/i', $data)) {
+ $status = "phpDynDNS: (Success) IP Address Updated Successfully!";
+ $successful_update = true;
+ } else {
+ $status = "phpDynDNS: (Unknown Response)";
+ log_error("phpDynDNS: PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'dnsimple':
+ /* Responds with HTTP 200 on success.
+ Responds with HTTP 4xx on error.
+ Returns JSON data as body */
+ $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
+ $header = substr($data, 0, $header_size);
+ $body = substr($data, $header_size);
+ if (preg_match("/Status: 200\s/i", $header)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Updated Successfully!";
+ $successful_update = true;
+ } else if (preg_match("/Status: 4\d\d\s/i", $header)) {
+ $arrbody = json_decode($body, true);
+ $message = $arrbody['message'] . ".";
+ if (isset($arrbody['errors']['content'])) {
+ foreach ($arrbody['errors']['content'] as $key => $content) {
+ $message .= " " . $content . ".";
+ }
+ }
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) " . $message;
+ } else {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)";
+ log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$body}");
+ $this->_debug($body);
+ }
+ break;
+ case 'googledomains':
+ if (preg_match('/notfqdn/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Not A FQDN";
+ } else if (preg_match('/nochg/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) No Change In IP Address";
+ $successful_update = true;
+ } else if (preg_match('/good/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Success) IP Address Changed Successfully! (" . $this->_dnsIP . ")";
+ $successful_update = true;
+ } else if (preg_match('/badauth/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) User Authorization Failed";
+ } else if (preg_match('/nohost/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Hostname does not exist or DynDNS not enabled";
+ } else if (preg_match('/badagent/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Bad request";
+ } else if (preg_match('/abuse/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Dynamic DNS access has been blocked!";
+ } else if (preg_match('/911/i', $data)) {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Error on Google's end, retry in 5 minutes";
+ } else {
+ $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)";
+ log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}");
+ $this->_debug($data);
+ }
+ break;
+ case 'dnsmadeeasy':
+ switch ($data) {
+ case 'success':
+ $status = "phpDynDNS({$this->_dnsHost}): (Success) IP Address Changed Successfully! (" . $this->_dnsIP . ")";
+ $successful_update = true;
+ break;
+ case 'error-auth':
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Invalid username or password";
+ break;
+ case 'error-auth-suspend':
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Account suspended";
+ break;
+ case 'error-auth-voided':
+ $status = "phpDynDNS ({$this->_dnsHost}): (Error) Account revoked";
+ break;
+ case 'error-record-invalid':
+ $status = "phpDynDns ({$this->_dnsHost}): (Error) Record does not exist in the system. Unable to update record";
+ break;
+ case 'error-record-auth':
+ $status = "phpDynDns ({$this->_dnsHost}): (Error) User does not have access to this record";
+ break;
+ case 'error-record-ip-same':
+ $status = "phpDynDns ({$this->_dnsHost}): (Success) No change in IP Address";
+ $successful_update = true;
+ break;
+ case 'error-system':
+ $status = "phpDynDns ({$this->_dnsHost}): (Error) General system error recognized by the system";
+ break;
+ case 'error':
+ $status = "phpDynDns ({$this->_dnsHost}): (Error) General system error unrecognized by the system";
+ break;
+ default:
+ $status = "phpDynDNS ({$this->_dnsHost}): (Unknown Response)";
+ log_error("phpDynDNS ({$this->_dnsHost}): PAYLOAD: {$data}");
+ $this->_debug($data);
+ break;
+ }
+ break;
+ }
+
+ if ($successful_update == true) {
+ /* Write WAN IP to cache file */
+ $wan_ip = $this->_checkIP();
+ conf_mount_rw();
+ if ($this->_useIPv6 == false && $wan_ip > 0) {
+ $currentTime = time();
+ notify_all_remote(sprintf(gettext("DynDNS updated IP Address on %s (%s) to %s"), convert_real_interface_to_friendly_descr($this->_if), $this->_if, $wan_ip));
+ log_error("phpDynDNS: updating cache file {$this->_cacheFile}: {$wan_ip}");
+ @file_put_contents($this->_cacheFile, "{$wan_ip}:{$currentTime}");
+ } else {
+ @unlink($this->_cacheFile);
+ }
+ if ($this->_useIPv6 == true && $wan_ip > 0) {
+ $currentTime = time();
+ notify_all_remote(sprintf(gettext("DynDNS updated IPv6 Address on %s (%s) to %s"), convert_real_interface_to_friendly_descr($this->_if), $this->_if, $wan_ip));
+ log_error("phpDynDNS: updating cache file {$this->_cacheFile_v6}: {$wan_ip}");
+ @file_put_contents($this->_cacheFile_v6, "{$wan_ip}|{$currentTime}");
+ } else {
+ @unlink($this->_cacheFile_v6);
+ }
+ conf_mount_ro();
+ }
+ $this->status = $status;
+ log_error($status);
+ }
+
+ /*
+ * Private Function (added 12 July 05) [beta]
+ * Return Error, Set Last Error, and Die.
+ */
+ function _error($errorNumber = '1') {
+ switch ($errorNumber) {
+ case 0:
+ break;
+ case 2:
+ $error = 'phpDynDNS: (ERROR!) No Dynamic DNS Service provider was selected.';
+ break;
+ case 3:
+ $error = 'phpDynDNS: (ERROR!) No Username Provided.';
+ break;
+ case 4:
+ $error = 'phpDynDNS: (ERROR!) No Password Provided.';
+ break;
+ case 5:
+ $error = 'phpDynDNS: (ERROR!) No Hostname Provided.';
+ break;
+ case 6:
+ $error = 'phpDynDNS: (ERROR!) The Dynamic DNS Service provided is not yet supported.';
+ break;
+ case 7:
+ $error = 'phpDynDNS: (ERROR!) No Update URL Provided.';
+ break;
+ case 8:
+ $status = "Route 53: (Error) Invalid ZoneID";
+ break;
+ case 9:
+ $status = "Route 53: (Error) Invalid TTL";
+ break;
+ case 10:
+ $error = "phpDynDNS ({$this->_dnsHost}): No change in my IP address and/or " . $this->_dnsMaxCacheAgeDays . " days has not passed. Not updating dynamic DNS entry.";
+ break;
+ default:
+ $error = "phpDynDNS: (ERROR!) Unknown Response.";
+ /* FIXME: $data isn't in scope here */
+ /* $this->_debug($data); */
+ break;
+ }
+ $this->lastError = $error;
+ log_error($error);
+ }
+
+ /*
+ * Private Function (added 12 July 05) [beta]
+ * - Detect whether or not IP needs to be updated.
+ * | Written Specifically for pfSense (https://www.pfsense.org) may
+ * | work with other systems. pfSense base is FreeBSD.
+ */
+ function _detectChange() {
+ global $debug;
+
+ if ($debug) {
+ log_error("DynDns ({$this->_dnsHost}): _detectChange() starting.");
+ }
+
+ $currentTime = time();
+
+ $wan_ip = $this->_checkIP();
+ if ($wan_ip == 0) {
+ log_error("DynDns ({$this->_dnsHost}): Current WAN IP could not be determined, skipping update process.");
+ return false;
+ }
+ $log_error = "DynDns ({$this->_dnsHost}): Current WAN IP: {$wan_ip} ";
+
+ if ($this->_useIPv6 == true) {
+ if (file_exists($this->_cacheFile_v6)) {
+ $contents = file_get_contents($this->_cacheFile_v6);
+ list($cacheIP, $cacheTime) = explode('|', $contents);
+ $this->_debug($cacheIP.'/'.$cacheTime);
+ $initial = false;
+ $log_error .= "Cached IPv6: {$cacheIP} ";
+ } else {
+ conf_mount_rw();
+ $cacheIP = '::';
+ @file_put_contents($this->_cacheFile, "::|{$currentTime}");
+ conf_mount_ro();
+ $cacheTime = $currentTime;
+ $initial = true;
+ $log_error .= "No Cached IPv6 found.";
+ }
+ } else {
+ if (file_exists($this->_cacheFile)) {
+ $contents = file_get_contents($this->_cacheFile);
+ list($cacheIP, $cacheTime) = explode(':', $contents);
+ $this->_debug($cacheIP.'/'.$cacheTime);
+ $initial = false;
+ $log_error .= "Cached IP: {$cacheIP} ";
+ } else {
+ conf_mount_rw();
+ $cacheIP = '0.0.0.0';
+ @file_put_contents($this->_cacheFile, "0.0.0.0:{$currentTime}");
+ conf_mount_ro();
+ $cacheTime = $currentTime;
+ $initial = true;
+ $log_error .= "No Cached IP found.";
+ }
+ }
+ if ($this->_dnsVerboseLog) {
+ log_error($log_error);
+ }
+
+ // Convert seconds = days * hr/day * min/hr * sec/min
+ $maxCacheAgeSecs = $this->_dnsMaxCacheAgeDays * 24 * 60 * 60;
+
+ $needs_updating = FALSE;
+ /* lets determine if the item needs updating */
+ if ($cacheIP != $wan_ip) {
+ $needs_updating = true;
+ $update_reason = "DynDns: cacheIP != wan_ip. Updating. ";
+ $update_reason .= "Cached IP: {$cacheIP} WAN IP: {$wan_ip} ";
+ }
+ if (($currentTime - $cacheTime) > $maxCacheAgeSecs) {
+ $needs_updating = true;
+ $this->_forceUpdateNeeded = true;
+ $update_reason = "DynDns: More than " . $this->_dnsMaxCacheAgeDays . " days. Updating. ";
+ $update_reason .= "{$currentTime} - {$cacheTime} > {$maxCacheAgeSecs} ";
+ }
+ if ($initial == true) {
+ $needs_updating = true;
+ $update_reason .= "Initial update. ";
+ }
+
+ /* finally if we need updating then store the
+ * new cache value and return true
+ */
+ if ($needs_updating == true) {
+ if ($this->_dnsVerboseLog) {
+ log_error("DynDns ({$this->_dnsHost}): {$update_reason}");
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /*
+ * Private Function (added 16 July 05) [beta]
+ * - Writes debug information to a file.
+ * - This function is only called when a unknown response
+ * - status is returned from a DynDNS service provider.
+ */
+ function _debug($data) {
+ global $g;
+
+ if (!$g['debug']) {
+ return;
+ }
+ $string = date('m-d-y h:i:s').' - ('.$this->_debugID.') - ['.$this->_dnsService.'] - '.$data."\n";
+ conf_mount_rw();
+ $file = fopen($this->_debugFile, 'a');
+ fwrite($file, $string);
+ fclose($file);
+ conf_mount_ro();
+ }
+ function _checkIP() {
+ global $debug;
+
+ if ($debug) {
+ log_error("DynDns ({$this->_dnsHost}): _checkIP() starting.");
+ }
+
+ if ($this->_useIPv6 == true) {
+ $ip_address = find_interface_ipv6($this->_if);
+ if (!is_ipaddrv6($ip_address)) {
+ return 0;
+ }
+ } else {
+ $ip_address = find_interface_ip($this->_if);
+ if (!is_ipaddr($ip_address)) {
+ return 0;
+ }
+ }
+ if ($this->_useIPv6 == false && is_private_ip($ip_address)) {
+ $hosttocheck = "checkip.dyndns.org";
+ $try = 0;
+ while ($try < 3) {
+ $checkip = gethostbyname($hosttocheck);
+ if (is_ipaddr($checkip)) {
+ break;
+ }
+ $try++;
+ }
+ if ($try >= 3) {
+ log_error("Dyndns debug information ({$this->_dnsHost}): Could not resolve {$hosttocheck} to IP using interface IP {$ip_address}.");
+ return 0;
+ }
+ $ip_ch = curl_init("http://{$checkip}");
+ curl_setopt($ip_ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ip_ch, CURLOPT_SSL_VERIFYPEER, FALSE);
+ curl_setopt($ip_ch, CURLOPT_INTERFACE, 'host!' . $ip_address);
+ curl_setopt($ip_ch, CURLOPT_CONNECTTIMEOUT, '30');
+ curl_setopt($ip_ch, CURLOPT_TIMEOUT, 120);
+ if ($this->_useIPv6 == false) {
+ curl_setopt($ip_ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
+ }
+ $ip_result_page = curl_exec($ip_ch);
+ curl_close($ip_ch);
+ $ip_result_decoded = urldecode($ip_result_page);
+ preg_match('/Current IP Address: (.*)<\/body>/', $ip_result_decoded, $matches);
+ $ip_address = trim($matches[1]);
+ if (is_ipaddr($ip_address)) {
+ if ($this->_dnsVerboseLog) {
+ log_error("DynDns ({$this->_dnsHost}): {$ip_address} extracted from {$hosttocheck}");
+ }
+ } else {
+ log_error("DynDns ({$this->_dnsHost}): IP address could not be extracted from {$hosttocheck}");
+ return 0;
+ }
+ } else {
+ if ($this->_dnsVerboseLog) {
+ log_error("DynDns ({$this->_dnsHost}): {$ip_address} extracted from local system.");
+ }
+ }
+ $this->_dnsIP = $ip_address;
+
+ return $ip_address;
+ }
+
+ }
+
+?>
diff --git a/src/etc/inc/easyrule.inc b/src/etc/inc/easyrule.inc
new file mode 100644
index 0000000..c46e84d
--- /dev/null
+++ b/src/etc/inc/easyrule.inc
@@ -0,0 +1,495 @@
+<?php
+/*
+ easyrule.inc
+
+ Copyright (C) 2009-2010 Jim Pingle (jpingle@gmail.com)
+ Originally Sponsored By Anathematic @ pfSense Forums
+ 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.
+*/
+/*
+ pfSense_BUILDER_BINARIES:
+ pfSense_MODULE: filter
+*/
+
+$blockaliasname = 'EasyRuleBlockHosts';
+$protocols_with_ports = array('tcp', 'udp');
+require_once("functions.inc");
+require_once("util.inc");
+require_once("config.inc");
+
+function easyrule_find_rule_interface($int) {
+ global $config;
+ /* Borrowed from firewall_rules.php */
+ $iflist = get_configured_interface_with_descr(false, true);
+
+ if ($config['pptpd']['mode'] == "server") {
+ $iflist['pptp'] = "PPTP VPN";
+ }
+
+ if ($config['pppoe']['mode'] == "server") {
+ $iflist['pppoe'] = "PPPoE Server";
+ }
+
+ if ($config['l2tp']['mode'] == "server") {
+ $iflist['l2tp'] = "L2TP VPN";
+ }
+
+ /* add ipsec interfaces */
+ if (isset($config['ipsec']['enable']) || isset($config['ipsec']['client']['enable'])) {
+ $iflist["enc0"] = "IPSEC";
+ }
+
+ if (isset($iflist[$int])) {
+ return $int;
+ }
+
+ foreach ($iflist as $if => $ifd) {
+ if (strtolower($int) == strtolower($ifd)) {
+ return $if;
+ }
+ }
+
+ if (substr($int, 0, 4) == "ovpn") {
+ return "openvpn";
+ }
+
+ return false;
+}
+
+function easyrule_block_rule_exists($int = 'wan', $ipproto = "inet") {
+ global $blockaliasname, $config;
+ /* No rules, we we know it doesn't exist */
+ if (!is_array($config['filter']['rule'])) {
+ return false;
+ }
+
+ /* Search through the rules for one referencing our alias */
+ foreach ($config['filter']['rule'] as $rule) {
+ if (!is_array($rule) || !is_array($rule['source'])) {
+ continue;
+ }
+ $checkproto = isset($rule['ipprotocol']) ? $rule['ipprotocol'] : "inet";
+ if ($rule['source']['address'] == $blockaliasname . strtoupper($int) && ($rule['interface'] == $int) && ($checkproto == $ipproto)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function easyrule_block_rule_create($int = 'wan', $ipproto = "inet") {
+ global $blockaliasname, $config;
+ /* If the alias doesn't exist, exit.
+ * Can't create an empty alias, and we don't know a host */
+ if (easyrule_block_alias_getid($int) === false) {
+ return false;
+ }
+
+ /* If the rule already exists, no need to do it again */
+ if (easyrule_block_rule_exists($int, $ipproto)) {
+ return true;
+ }
+
+ /* No rules, start a new array */
+ if (!is_array($config['filter']['rule'])) {
+ $config['filter']['rule'] = array();
+ }
+
+ filter_rules_sort();
+ $a_filter = &$config['filter']['rule'];
+
+ /* Make up a new rule */
+ $filterent = array();
+ $filterent['type'] = 'block';
+ $filterent['interface'] = $int;
+ $filterent['ipprotocol'] = $ipproto;
+ $filterent['source']['address'] = $blockaliasname . strtoupper($int);
+ $filterent['destination']['any'] = '';
+ $filterent['descr'] = gettext("Easy Rule: Blocked from Firewall Log View");
+ $filterent['created'] = make_config_revision_entry(null, gettext("Easy Rule"));
+
+ array_splice($a_filter, 0, 0, array($filterent));
+
+ return true;
+}
+
+function easyrule_block_alias_getid($int = 'wan') {
+ global $blockaliasname, $config;
+ if (!is_array($config['aliases'])) {
+ return false;
+ }
+
+ /* Hunt down an alias with the name we want, return its id */
+ foreach ($config['aliases']['alias'] as $aliasid => $alias) {
+ if ($alias['name'] == $blockaliasname . strtoupper($int)) {
+ return $aliasid;
+ }
+ }
+
+ return false;
+}
+
+function easyrule_block_alias_add($host, $int = 'wan') {
+ global $blockaliasname, $config;
+ /* If the host isn't a valid IP address, bail */
+ $host = trim($host, "[]");
+ if (!is_ipaddr($host) && !is_subnet($host)) {
+ return false;
+ }
+
+ /* If there are no aliases, start an array */
+ if (!is_array($config['aliases']['alias'])) {
+ $config['aliases']['alias'] = array();
+ }
+
+ $a_aliases = &$config['aliases']['alias'];
+
+ /* Try to get the ID if the alias already exists */
+ $id = easyrule_block_alias_getid($int);
+ if ($id === false) {
+ unset($id);
+ }
+
+ $alias = array();
+
+ if (is_subnet($host)) {
+ list($host, $mask) = explode("/", $host);
+ } elseif (is_specialnet($host)) {
+ $mask = 0;
+ } elseif (is_ipaddrv6($host)) {
+ $mask = 128;
+ } else {
+ $mask = 32;
+ }
+
+ if (isset($id) && $a_aliases[$id]) {
+
+ // Catch case when the list is empty
+ if (empty($a_aliases[$id]['address'])) {
+ $a_address = array();
+ $a_detail = array();
+ } else {
+ $a_address = explode(" ", $a_aliases[$id]['address']);
+
+ /* Make sure this IP isn't already in the list. */
+ if (in_array($host.'/'.$mask, $a_address)) {
+ return true;
+ }
+ $a_detail = explode("||", $a_aliases[$id]['detail']);
+ }
+
+ /* Since the alias already exists, just add to it. */
+ $alias['name'] = $a_aliases[$id]['name'];
+ $alias['type'] = $a_aliases[$id]['type'];
+ $alias['descr'] = $a_aliases[$id]['descr'];
+
+ $a_address[] = $host.'/'.$mask;
+ $a_detail[] = gettext('Entry added') . ' ' . date('r');
+
+ $alias['address'] = join(" ", $a_address);
+ $alias['detail'] = join("||", $a_detail);
+
+ } else {
+ /* Create a new alias with all the proper information */
+ $alias['name'] = $blockaliasname . strtoupper($int);
+ $alias['type'] = 'network';
+ $alias['descr'] = gettext("Hosts blocked from Firewall Log view");
+
+ $alias['address'] = $host . '/' . $mask;
+ $alias['detail'] = gettext('Entry added') . ' ' . date('r') . '||';
+ }
+
+ /* Replace the old alias if needed, otherwise tack it on the end */
+ if (isset($id) && $a_aliases[$id]) {
+ $a_aliases[$id] = $alias;
+ } else {
+ $a_aliases[] = $alias;
+ }
+
+ // Sort list
+ $a_aliases = msort($a_aliases, "name");
+
+ return true;
+}
+
+function easyrule_block_host_add($host, $int = 'wan', $ipproto = "inet") {
+ global $retval;
+ /* Bail if the supplied host is not a valid IP address */
+ $host = trim($host, "[]");
+ if (!is_ipaddr($host) && !is_subnet($host)) {
+ return false;
+ }
+
+ /* Flag whether or not we need to reload the filter */
+ $dirty = false;
+
+ /* Attempt to add this host to the alias */
+ if (easyrule_block_alias_add($host, $int)) {
+ $dirty = true;
+ } else {
+ /* Couldn't add the alias, or adding the host failed. */
+ return false;
+ }
+
+ /* Attempt to add the firewall rule if it doesn't exist.
+ * Failing to add the rule isn't necessarily an error, it may
+ * have been modified by the user in some way. Adding to the
+ * Alias is what's important.
+ */
+ if (!easyrule_block_rule_exists($int, $ipproto)) {
+ if (easyrule_block_rule_create($int, $ipproto)) {
+ $dirty = true;
+ } else {
+ return false;
+ }
+ }
+
+ /* If needed, write the config and reload the filter */
+ if ($dirty) {
+ write_config();
+ $retval = filter_configure();
+ if (!empty($_SERVER['DOCUMENT_ROOT'])) {
+ header("Location: firewall_aliases.php");
+ exit;
+ } else {
+ return true;
+ }
+ } else {
+ return false;
+ }
+}
+
+function easyrule_pass_rule_add($int, $proto, $srchost, $dsthost, $dstport, $ipproto) {
+ global $config;
+
+ /* No rules, start a new array */
+ if (!is_array($config['filter']['rule'])) {
+ $config['filter']['rule'] = array();
+ }
+
+ filter_rules_sort();
+ $a_filter = &$config['filter']['rule'];
+
+ /* Make up a new rule */
+ $filterent = array();
+ $filterent['type'] = 'pass';
+ $filterent['interface'] = $int;
+ $filterent['ipprotocol'] = $ipproto;
+ $filterent['descr'] = gettext("Easy Rule: Passed from Firewall Log View");
+
+ if ($proto != "any") {
+ $filterent['protocol'] = $proto;
+ } else {
+ unset($filterent['protocol']);
+ }
+
+ /* Default to only allow echo requests, since that's what most people want and
+ * it should be a safe choice. */
+ if ($proto == "icmp") {
+ $filterent['icmptype'] = 'echoreq';
+ }
+
+ if ((strtolower($proto) == "icmp6") || (strtolower($proto) == "icmpv6")) {
+ $filterent['protocol'] = "icmp";
+ }
+
+ if (is_subnet($srchost)) {
+ list($srchost, $srcmask) = explode("/", $srchost);
+ } elseif (is_specialnet($srchost)) {
+ $srcmask = 0;
+ } elseif (is_ipaddrv6($srchost)) {
+ $srcmask = 128;
+ } else {
+ $srcmask = 32;
+ }
+
+ if (is_subnet($dsthost)) {
+ list($dsthost, $dstmask) = explode("/", $dsthost);
+ } elseif (is_specialnet($dsthost)) {
+ $dstmask = 0;
+ } elseif (is_ipaddrv6($dsthost)) {
+ $dstmask = 128;
+ } else {
+ $dstmask = 32;
+ }
+
+ pconfig_to_address($filterent['source'], $srchost, $srcmask);
+ pconfig_to_address($filterent['destination'], $dsthost, $dstmask, '', $dstport, $dstport);
+
+ $filterent['created'] = make_config_revision_entry(null, gettext("Easy Rule"));
+ $a_filter[] = $filterent;
+
+ write_config($filterent['descr']);
+ $retval = filter_configure();
+ if (!empty($_SERVER['DOCUMENT_ROOT'])) {
+ header("Location: firewall_rules.php?if={$int}");
+ exit;
+ } else {
+ return true;
+ }
+}
+
+function easyrule_parse_block($int, $src, $ipproto = "inet") {
+ if (!empty($src) && !empty($int)) {
+ $src = trim($src, "[]");
+ if (!is_ipaddr($src) && !is_subnet($src)) {
+ return gettext("Tried to block invalid IP:") . ' ' . htmlspecialchars($src);
+ }
+ $int = easyrule_find_rule_interface($int);
+ if ($int === false) {
+ return gettext("Invalid interface for block rule:") . ' ' . htmlspecialchars($int);
+ }
+ if (easyrule_block_host_add($src, $int, $ipproto)) {
+ return gettext("Host added successfully");
+ } else {
+ return gettext("Failed to create block rule, alias, or add host.");
+ }
+ } else {
+ return gettext("Tried to block but had no host IP or interface");
+ }
+ return gettext("Unknown block error.");
+}
+
+function easyrule_parse_unblock($int, $host, $ipproto = "inet") {
+ global $blockaliasname, $config;
+
+ if (!empty($host) && !empty($int)) {
+ $host = trim($host, "[]");
+ if (!is_ipaddr($host) && !is_subnet($host)) {
+ return gettext("Tried to unblock invalid IP:") . ' ' . htmlspecialchars($host);
+ }
+ $real_int = easyrule_find_rule_interface($int);
+ if ($real_int === false) {
+ return gettext("Invalid interface for block rule:") . ' ' . htmlspecialchars($int);
+ }
+
+ /* Try to get the ID - will fail if there are no rules/alias on this interface */
+ $id = easyrule_block_alias_getid($real_int);
+ if ($id === false || !$config['aliases']['alias'][$id]) {
+ return gettext("No block rules set on interface:") . ' ' . htmlspecialchars($int);
+ }
+
+ $alias = &$config['aliases']['alias'][$id];
+
+ if (is_subnet($host)) {
+ list($host, $mask) = explode("/", $host);
+ } elseif (is_specialnet($host)) {
+ $mask = 0;
+ } elseif (is_ipaddrv6($host)) {
+ $mask = 128;
+ } else {
+ $mask = 32;
+ }
+
+ // Create the expected string representation
+ $unblock = $host.'/'.$mask;
+
+ $a_address = explode(" ", $config['aliases']['alias'][$id]['address']);
+ $a_detail = explode("||", $config['aliases']['alias'][$id]['detail']);
+
+ if (($key = array_search($unblock, $a_address)) !== false) {
+ unset($a_address[$key]);
+ unset($a_detail[$key]);
+ // Write back the result to the config array
+ $config['aliases']['alias'][$id]['address'] = join(" ", $a_address);
+ $config['aliases']['alias'][$id]['detail'] = join("||", $a_detail);
+
+ // Update config
+ write_config();
+ $retval = filter_configure();
+ if (!empty($_SERVER['DOCUMENT_ROOT'])) {
+ header("Location: firewall_aliases.php");
+ exit;
+ } else {
+ return gettext("Host unblocked successfully");
+ }
+ } else {
+ return gettext("Host ist not on block list: " . $host);
+ }
+ }
+
+ return gettext("Tried to unblock but had no host IP or interface");
+
+}
+
+function easyrule_parse_getblock($int = 'wan', $sep = "\n") {
+ global $blockaliasname, $config;
+
+ $real_int = easyrule_find_rule_interface($int);
+ if ($real_int === false) {
+ return gettext("Invalid interface for block rule:") . ' ' . htmlspecialchars($int);
+ }
+
+ /* Try to get the ID - will fail if there are no rules/alias on this interface */
+ $id = easyrule_block_alias_getid($real_int);
+
+ if ($id === false || !$config['aliases']['alias'][$id] || empty($config['aliases']['alias'][$id]['address'])) {
+ return gettext("No block rules set on interface:") . ' ' . htmlspecialchars($int);
+ }
+ return join($sep, explode(" ", $config['aliases']['alias'][$id]['address']));
+
+}
+
+function easyrule_parse_pass($int, $proto, $src, $dst, $dstport = 0, $ipproto = "inet") {
+ /* Check for valid int, srchost, dsthost, dstport, and proto */
+ global $protocols_with_ports;
+ $src = trim($src, "[]");
+ $dst = trim($dst, "[]");
+
+ if (!empty($int) && !empty($proto) && !empty($src) && !empty($dst)) {
+ $int = easyrule_find_rule_interface($int);
+ if ($int === false) {
+ return gettext("Invalid interface for pass rule:") . ' ' . htmlspecialchars($int);
+ }
+ if (getprotobyname($proto) == -1) {
+ return gettext("Invalid protocol for pass rule:") . ' ' . htmlspecialchars($proto);
+ }
+ if (!is_ipaddr($src) && !is_subnet($src) && !is_ipaddroralias($src) && !is_specialnet($src)) {
+ return gettext("Tried to pass invalid source IP:") . ' ' . htmlspecialchars($src);
+ }
+ if (!is_ipaddr($dst) && !is_subnet($dst) && !is_ipaddroralias($dst) && !is_specialnet($dst)) {
+ return gettext("Tried to pass invalid destination IP:") . ' ' . htmlspecialchars($dst);
+ }
+ if (in_array($proto, $protocols_with_ports)) {
+ if (empty($dstport)) {
+ return gettext("Missing destination port:") . ' ' . htmlspecialchars($dstport);
+ }
+ if (!is_port($dstport) && ($dstport != "any")) {
+ return gettext("Tried to pass invalid destination port:") . ' ' . htmlspecialchars($dstport);
+ }
+ } else {
+ $dstport = 0;
+ }
+ /* Should have valid input... */
+ if (easyrule_pass_rule_add($int, $proto, $src, $dst, $dstport, $ipproto)) {
+ return gettext("Successfully added pass rule!");
+ } else {
+ return gettext("Failed to add pass rule.");
+ }
+ } else {
+ return gettext("Missing parameters for pass rule.");
+ }
+ return gettext("Unknown pass error.");
+}
+
+?>
diff --git a/src/etc/inc/filter.inc b/src/etc/inc/filter.inc
new file mode 100644
index 0000000..36bbe2b
--- /dev/null
+++ b/src/etc/inc/filter.inc
@@ -0,0 +1,4228 @@
+<?php
+/* $Id$ */
+/*
+ filter.inc
+ Copyright (C) 2004-2006 Scott Ullrich
+ Copyright (C) 2005 Bill Marquette
+ Copyright (C) 2006 Peter Allgeyer
+ Copyright (C) 2008-2010 Ermal Luçi
+ All rights reserved.
+
+ originally part of m0n0wall (http://m0n0.ch/wall)
+ 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.
+
+ pfSense_BUILDER_BINARIES: /sbin/kldload /usr/sbin/tcpdump /sbin/pfctl /bin/rm
+ pfSense_BUILDER_BINARIES: /usr/sbin/inetd
+ pfSense_MODULE: filter
+*/
+
+
+/* holds the items that will be executed *AFTER* the filter is fully loaded */
+$after_filter_configure_run = array();
+
+/* For installing cron job of schedules */
+$time_based_rules = false;
+
+/* Used to hold the interface list that will be used on ruleset creation. */
+$FilterIflist = array();
+
+/* Create a global array to avoid errors on rulesets. */
+$GatewaysList = array();
+
+/* Used for the hostname dns resolver */
+$filterdns = array();
+
+/* Used for aliases and interface macros */
+$aliases = "";
+
+/* ICMP v4 types */
+$icmptypes = array(
+ "" => gettext("any"),
+ "echoreq" => gettext("Echo request"),
+ "echorep" => gettext("Echo reply"),
+ "unreach" => gettext("Destination unreachable"),
+ "squench" => gettext("Source quench"),
+ "redir" => gettext("Redirect"),
+ "althost" => gettext("Alternate Host"),
+ "routeradv" => gettext("Router advertisement"),
+ "routersol" => gettext("Router solicitation"),
+ "timex" => gettext("Time exceeded"),
+ "paramprob" => gettext("Invalid IP header"),
+ "timereq" => gettext("Timestamp"),
+ "timerep" => gettext("Timestamp reply"),
+ "inforeq" => gettext("Information request"),
+ "inforep" => gettext("Information reply"),
+ "maskreq" => gettext("Address mask request"),
+ "maskrep" => gettext("Address mask reply"),
+ "trace" => gettext("Traceroute"),
+ "dataconv" => gettext("Datagram conversion error"),
+ "mobredir" => gettext("Mobile host redirect"),
+ "ipv6-where" => gettext("IPv6 where-are-you"),
+ "ipv6-here" => gettext("IPv6 I-am-here"),
+ "mobregreq" => gettext("Mobile registration request"),
+ "mobregrep" => gettext("Mobile registration reply"),
+ "skip" => gettext("SKIP"),
+ "photuris" => gettext("Photuris")
+);
+
+/* ICMP v6 types */
+$icmp6types = array(
+ "" => gettext("any"),
+ "unreach" => gettext("Destination unreachable"),
+ "toobig" => gettext("Packet too big"),
+ "timex" => gettext("Time exceeded"),
+ "paramprob" => gettext("Parameter problem"),
+ "echoreq" => gettext("Echo request"),
+ "echorep" => gettext("Echo reply"),
+ "groupqry" => gettext("Group membership query"),
+ "listqry" => gettext("Multicast listener query"),
+ "grouprep" => gettext("Group membership report"),
+ "listenrep" => gettext("Multicast listener report"),
+ "groupterm" => gettext("Group membership termination"),
+ "listendone" => gettext("Multicast listener done"),
+ "routersol" => gettext("Router solicitation"),
+ "routeradv" => gettext("Router advertisement"),
+ "neighbrsol" => gettext("Neighbor solicitation"),
+ "neighbradv" => gettext("Neighbor advertisement"),
+ "redir" => gettext("Redirect"),
+ "routrrenum" => gettext("Router renumbering"),
+ "wrureq" => gettext("Who are you request"),
+ "wrurep" => gettext("Who are you reply"),
+ "fqdnreq" => gettext("FQDN query"),
+ "fqdnrep" => gettext("FQDN reply"),
+ "niqry" => gettext("Node information request"),
+ "nirep" => gettext("Node information reply"),
+ "mtraceresp" => gettext("mtrace resp"),
+ "mtrace" => gettext("mtrace messages")
+);
+
+global $tracker;
+global $negate_tracker;
+$tracker = 1000000000;
+$negate_tracker = 10000000;
+
+function filter_rule_tracker($tracker) {
+ global $tracker;
+
+ return (++$tracker);
+}
+
+function filter_negaterule_tracker() {
+ global $negate_tracker;
+
+ ++$negate_tracker;
+ return "tracker {$negate_tracker} ";
+}
+
+function fix_rule_label($descr) {
+ $descr = str_replace('"', '', $descr);
+ if (strlen($descr) > 63) {
+ return substr($descr, 0, 60) . "...";
+ } else {
+ return $descr;
+ }
+}
+
+function is_bogonsv6_used() {
+ global $config, $g;
+ # Only use bogonsv6 table if IPv6 Allow is on, and at least 1 enabled interface also has "blockbogons" enabled.
+ $usebogonsv6 = false;
+ if (isset($config['system']['ipv6allow'])) {
+ foreach ($config['interfaces'] as $ifacedata) {
+ if (isset($ifacedata['enable']) && isset($ifacedata['blockbogons'])) {
+ $usebogonsv6 = true;
+ break;
+ }
+ }
+ }
+ return $usebogonsv6;
+}
+
+function filter_pflog_start($kill_first = false) {
+ global $config, $g;
+
+ if (isset($config['system']['developerspew'])) {
+ $mt = microtime();
+ echo "filter_pflog_start() being called $mt\n";
+ }
+ if ((!file_exists("{$g['varrun_path']}/filterlog.pid")) ||
+ (!isvalidpid("{$g['varrun_path']}/filterlog.pid"))) {
+ mwexec("/usr/local/sbin/filterlog -i pflog0 -p {$g['varrun_path']}/filterlog.pid");
+ }
+}
+
+/* reload filter async */
+function filter_configure() {
+ global $g;
+
+ if (isset($config['system']['developerspew'])) {
+ $mt = microtime();
+ echo "filter_configure() being called $mt\n";
+ }
+
+ /*
+ * NOTE: Check here for bootup status since this should not be triggered during bootup.
+ * The reason is that rc.bootup calls filter_configure_sync directly which does this too.
+ */
+ if (!platform_booting()) {
+ send_event("filter reload");
+ }
+}
+
+function filter_delete_states_for_down_gateways() {
+ global $config, $GatewaysList;
+
+ if (isset($config['system']['kill_states'])) {
+ return;
+ }
+
+ $any_gateway_down = false;
+ $a_gateways = return_gateways_status();
+ if (is_array($GatewaysList)) {
+ foreach ($GatewaysList as $gwname => $gateway) {
+ if (empty($gateway['monitor'])) {
+ continue;
+ }
+ if (!is_ipaddr($gateway['monitor'])) {
+ continue;
+ }
+ if (strstr($gateway['monitor'], "127.0.0.")) {
+ continue;
+ }
+ if (empty($a_gateways[$gateway['monitor']])) {
+ continue;
+ }
+ $gwstatus =& $a_gateways[$gateway['monitor']];
+ if (strstr($gwstatus['status'], "down")) {
+ $any_gateway_down = true;
+ break;
+ }
+ }
+ }
+ if ($any_gateway_down == true) {
+ mwexec("/sbin/pfctl -Fs");
+ }
+}
+
+/* reload filter sync */
+function filter_configure_sync($delete_states_if_needed = true) {
+ global $config, $g, $after_filter_configure_run, $FilterIflist;
+ global $time_based_rules, $filterdns, $aliases, $dummynet_name_list;
+
+ /* Use filter lock to not allow concurrent filter reloads during this run. */
+ $filterlck = lock('filter', LOCK_EX);
+
+ filter_pflog_start();
+ update_filter_reload_status(gettext("Initializing"));
+
+ /* invalidate interface cache */
+ get_interface_arr(true);
+
+ if (isset($config['system']['developerspew'])) {
+ $mt = microtime();
+ echo "filter_configure_sync() being called $mt\n";
+ }
+ /* Get interface list to work with. */
+ filter_generate_optcfg_array();
+ if (platform_booting() == true) {
+ echo gettext("Configuring firewall");
+ }
+
+ /* generate aliases */
+ if (platform_booting() == true) {
+ echo ".";
+ }
+ update_filter_reload_status(gettext("Creating aliases"));
+ $aliases = filter_generate_aliases();
+ $gateways = filter_generate_gateways();
+ if (platform_booting() == true) {
+ echo ".";
+ }
+ update_filter_reload_status(gettext("Generating Limiter rules"));
+ $dummynet_rules = filter_generate_dummynet_rules();
+ $dummynet_name_list = get_unique_dnqueue_list();
+ update_filter_reload_status(gettext("Generating NAT rules"));
+ /* generate nat rules */
+ $natrules = filter_nat_rules_generate();
+ if (platform_booting() == true) {
+ echo ".";
+ }
+ update_filter_reload_status(gettext("Generating filter rules"));
+ /* generate pfctl rules */
+ $pfrules = filter_rules_generate();
+ /* generate altq, limiter */
+ if (platform_booting() == true) {
+ echo ".";
+ }
+ update_filter_reload_status(gettext("Generating ALTQ queues"));
+ $altq_queues = filter_generate_altq_queues();
+ update_filter_reload_status(gettext("Generating Layer7 rules"));
+ generate_layer7_files();
+ if (platform_booting() == true) {
+ echo ".";
+ }
+ update_filter_reload_status(gettext("Loading filter rules"));
+ /* enable pf if we need to, otherwise disable */
+ if (!isset ($config['system']['disablefilter'])) {
+ mwexec("/sbin/pfctl -e", true);
+ } else {
+ mwexec("/sbin/pfctl -d", true);
+ unlink_if_exists("{$g['tmp_path']}/filter_loading");
+ update_filter_reload_status(gettext("Filter is disabled. Not loading rules."));
+ if (platform_booting() == true) {
+ echo gettext("done.") . "\n";
+ }
+ unlock($filterlck);
+ return;
+ }
+
+ $limitrules = "";
+ /* User defined maximum table entries in Advanced menu. */
+ if ($config['system']['maximumtableentries'] <> "" && is_numeric($config['system']['maximumtableentries'])) {
+ $limitrules .= "set limit table-entries {$config['system']['maximumtableentries']}\n";
+ }
+
+ if ($config['system']['optimization'] <> "") {
+ $limitrules .= "set optimization {$config['system']['optimization']}\n";
+ if ($config['system']['optimization'] == "conservative") {
+ $limitrules .= "set timeout { udp.first 300, udp.single 150, udp.multiple 900 }\n";
+ }
+ } else {
+ $limitrules .= "set optimization normal\n";
+ }
+
+ $timeoutlist = "";
+ if (isset($config['system']['tcpfirsttimeout']) && is_numericint($config['system']['tcpfirsttimeout'])) {
+ $timeoutlist .= " tcp.first {$config['system']['tcpfirsttimeout']} ";
+ }
+ if (isset($config['system']['tcpopeningtimeout']) && is_numericint($config['system']['tcpopeningtimeout'])) {
+ $timeoutlist .= " tcp.opening {$config['system']['tcpopeningtimeout']} ";
+ }
+ if (isset($config['system']['tcpestablishedtimeout']) && is_numericint($config['system']['tcpestablishedtimeout'])) {
+ $timeoutlist .= " tcp.established {$config['system']['tcpestablishedtimeout']} ";
+ }
+ if (isset($config['system']['tcpclosingtimeout']) && is_numericint($config['system']['tcpclosingtimeout'])) {
+ $timeoutlist .= " tcp.closing {$config['system']['tcpclosingtimeout']} ";
+ }
+ if (isset($config['system']['tcpfinwaittimeout']) && is_numericint($config['system']['tcpfinwaittimeout'])) {
+ $timeoutlist .= " tcp.finwait {$config['system']['tcpfinwaittimeout']} ";
+ }
+ if (isset($config['system']['tcpclosedtimeout']) && is_numericint($config['system']['tcpclosedtimeout'])) {
+ $timeoutlist .= " tcp.closed {$config['system']['tcpclosedtimeout']} ";
+ }
+ if (isset($config['system']['udpfirsttimeout']) && is_numericint($config['system']['udpfirsttimeout'])) {
+ $timeoutlist .= " udp.first {$config['system']['udpfirsttimeout']} ";
+ }
+ if (isset($config['system']['udpsingletimeout']) && is_numericint($config['system']['udpsingletimeout'])) {
+ $timeoutlist .= " udp.single {$config['system']['udpsingletimeout']} ";
+ }
+ if (isset($config['system']['udpmultipletimeout']) && is_numericint($config['system']['udpmultipletimeout'])) {
+ $timeoutlist .= " udp.multiple {$config['system']['udpmultipletimeout']} ";
+ }
+ if (isset($config['system']['icmpfirsttimeout']) && is_numericint($config['system']['icmpfirsttimeout'])) {
+ $timeoutlist .= " icmp.first {$config['system']['icmpfirsttimeout']} ";
+ }
+ if (isset($config['system']['icmperrortimeout']) && is_numericint($config['system']['icmperrortimeout'])) {
+ $timeoutlist .= " icmp.error {$config['system']['icmperrortimeout']} ";
+ }
+ if (isset($config['system']['otherfirsttimeout']) && is_numericint($config['system']['otherfirsttimeout'])) {
+ $timeoutlist .= " other.first {$config['system']['otherfirsttimeout']} ";
+ }
+ if (isset($config['system']['othersingletimeout']) && is_numericint($config['system']['othersingletimeout'])) {
+ $timeoutlist .= " other.single {$config['system']['othersingletimeout']} ";
+ }
+ if (isset($config['system']['othermultipletimeout']) && is_numericint($config['system']['othermultipletimeout'])) {
+ $timeoutlist .= " other.multiple {$config['system']['othermultipletimeout']} ";
+ }
+
+ if ($timeoutlist <> "") {
+ $limitrules .= "set timeout { $timeoutlist }\n";
+ }
+
+ if (!empty($config['system']['adaptivestart']) && !empty($config['system']['adaptiveend'])) {
+ $limitrules .= "set timeout { adaptive.start {$config['system']['adaptivestart']}, adaptive.end {$config['system']['adaptiveend']} }\n";
+ }
+
+ if ($config['system']['maximumstates'] <> "" && is_numeric($config['system']['maximumstates'])) {
+ /* User defined maximum states in Advanced menu. */
+ $limitrules .= "set limit states {$config['system']['maximumstates']}\n";
+ $limitrules .= "set limit src-nodes {$config['system']['maximumstates']}\n";
+ } else {
+ $max_states = pfsense_default_state_size();
+ $limitrules .= "set limit states {$max_states}\n";
+ $limitrules .= "set limit src-nodes {$max_states}\n";
+ }
+
+ /* Frag limit. pf default is 5000 */
+ if ($config['system']['maximumfrags'] <> "" && is_numeric($config['system']['maximumfrags'])) {
+ $limitrules .= "set limit frags {$config['system']['maximumfrags']}\n";
+ }
+
+ if (isset($config['system']['lb_use_sticky']) && is_numeric($config['system']['srctrack']) && ($config['system']['srctrack'] > 0)) {
+ $limitrules .= "set timeout src.track {$config['system']['srctrack']}\n";
+ }
+
+ $rules = "";
+ $rules = "{$limitrules}\n";
+ $rules .= "{$aliases} \n";
+ $rules .= "{$gateways} \n";
+ update_filter_reload_status(gettext("Setting up logging information"));
+ $rules .= filter_setup_logging_interfaces();
+ $rules .= "\n";
+ $rules .= "set skip on pfsync0\n";
+ $rules .= "\n";
+ update_filter_reload_status(gettext("Setting up SCRUB information"));
+ $rules .= filter_generate_scrubing();
+ $rules .= "\n";
+ $rules .= "{$altq_queues}\n";
+ $rules .= "{$natrules}\n";
+ $rules .= "{$pfrules}\n";
+ $rules .= discover_pkg_rules("filter");
+
+ unset($aliases, $gateways, $altq_queues, $natrules, $pfrules);
+
+ // Copy rules.debug to rules.debug.old
+ if (file_exists("{$g['tmp_path']}/rules.debug")) {
+ @copy("{$g['tmp_path']}/rules.debug", "{$g['tmp_path']}/rules.debug.old");
+ }
+
+ if (!@file_put_contents("{$g['tmp_path']}/rules.debug", $rules, LOCK_EX)) {
+ log_error("WARNING: Could not write new rules!");
+ unlock($filterlck);
+ return;
+ }
+
+ @file_put_contents("{$g['tmp_path']}/rules.limits", $limitrules);
+ mwexec("/sbin/pfctl -Of {$g['tmp_path']}/rules.limits");
+ unset($rules, $limitrules);
+
+ if (isset($config['system']['developerspew'])) {
+ $mt = microtime();
+ echo "pfctl being called at $mt\n";
+ }
+ unset($rules_loading, $rules_error);
+ $_grbg = exec("/sbin/pfctl -o basic -f {$g['tmp_path']}/rules.debug 2>&1", $rules_error, $rules_loading);
+ if (isset($config['system']['developerspew'])) {
+ $mt = microtime();
+ echo "pfctl done at $mt\n";
+ }
+ /*
+ * check for a error while loading the rules file. if an error has occurred
+ * then output the contents of the error to the caller
+ */
+ if ($rules_loading <> 0) {
+ $saved_line_error = $rules_error[0];
+ $line_error = explode(":", $rules_error[0]);
+ $line_number = $line_error[1];
+ $line_split = file("{$g['tmp_path']}/rules.debug");
+ if (is_array($line_split)) {
+ $line_error = sprintf(gettext('The line in question reads [%1$d]: %2$s'), $line_number, $line_split[$line_number-1]);
+ }
+ unset($line_split);
+
+ /* Brutal ugly hack but required -- PF is stuck, unwedge */
+ if (strstr("$rules_error[0]", "busy")) {
+ exec("/sbin/pfctl -d; /sbin/pfctl -e; /sbin/pfctl -f {$g['tmp_path']}/rules.debug");
+ $error_msg = gettext("PF was wedged/busy and has been reset.");
+ file_notice("pf_busy", $error_msg, "pf_busy", "");
+ } else {
+ $_grbg = exec("/sbin/pfctl -o basic -f {$g['tmp_path']}/rules.debug.old 2>&1");
+ }
+ unset($rules_loading, $rules_error);
+
+ if ($line_error and $line_number) {
+ file_notice("filter_load", sprintf(gettext('There were error(s) loading the rules: %1$s - %2$s'), $saved_line_error, $line_error), "Filter Reload", "");
+ update_filter_reload_status(sprintf(gettext('There were error(s) loading the rules: %1$s - %2$s'), $saved_line_error, $line_error));
+ unlock($filterlck);
+ return;
+ }
+ }
+
+ # If we are not using bogonsv6 then we can remove any bogonsv6 table from the running pf (if the table is not there, the kill is still fine).
+ if (!is_bogonsv6_used()) {
+ $_grbg = exec("/sbin/pfctl -t bogonsv6 -T kill 2>/dev/null");
+ }
+
+ update_filter_reload_status(gettext("Starting up layer7 daemon"));
+ layer7_start_l7daemon();
+
+ if (!platform_booting()) {
+ if (!empty($filterdns)) {
+ @file_put_contents("{$g['varetc_path']}/filterdns.conf", implode("", $filterdns));
+ unset($filterdns);
+ if (isvalidpid("{$g['varrun_path']}/filterdns.pid")) {
+ sigkillbypid("{$g['varrun_path']}/filterdns.pid", "HUP");
+ } else {
+ /*
+ * FilterDNS has three debugging levels. The default chosen is 1.
+ * Available are level 2 and greater then 2.
+ */
+ if (isset($config['system']['aliasesresolveinterval']) && is_numeric($config['system']['aliasesresolveinterval'])) {
+ $resolve_interval = $config['system']['aliasesresolveinterval'];
+ } else {
+ $resolve_interval = 300;
+ }
+ mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns.pid -i {$resolve_interval} -c {$g['varetc_path']}/filterdns.conf -d 1");
+ }
+ } else {
+ killbypid("{$g['varrun_path']}/filterdns.pid");
+ @unlink("{$g['varrun_path']}/filterdns.pid");
+ }
+ }
+
+ /* run items scheduled for after filter configure run */
+ $fda = fopen("{$g['tmp_path']}/commands.txt", "w");
+ if ($fda) {
+ if ($after_filter_configure_run) {
+ foreach ($after_filter_configure_run as $afcr) {
+ fwrite($fda, $afcr . "\n");
+ }
+ unset($after_filter_configure_run);
+ }
+
+ /*
+ * we need a way to let a user run a shell cmd after each
+ * filter_configure() call. run this xml command after
+ * each change.
+ */
+ if ($config['system']['afterfilterchangeshellcmd'] <> "") {
+ fwrite($fda, $config['system']['afterfilterchangeshellcmd'] . "\n");
+ }
+
+ fclose($fda);
+ }
+
+ if (file_exists("{$g['tmp_path']}/commands.txt")) {
+ mwexec("sh {$g['tmp_path']}/commands.txt &");
+ unlink("{$g['tmp_path']}/commands.txt");
+ }
+
+ /* if time based rules are enabled then swap in the set */
+ if ($time_based_rules == true) {
+ filter_tdr_install_cron(true);
+ } else {
+ filter_tdr_install_cron(false);
+ }
+
+ if (platform_booting() == true) {
+ echo ".";
+ }
+
+ if ($delete_states_if_needed) {
+ update_filter_reload_status(gettext("Processing down interface states"));
+ filter_delete_states_for_down_gateways();
+ }
+
+ update_filter_reload_status(gettext("Running plugins"));
+
+ if (is_dir("/usr/local/pkg/pf/")) {
+ /* process packager manager custom rules */
+ update_filter_reload_status(gettext("Running plugins (pf)"));
+ run_plugins("/usr/local/pkg/pf/");
+ update_filter_reload_status(gettext("Plugins completed."));
+ }
+
+ update_filter_reload_status(gettext("Done"));
+ if (platform_booting() == true) {
+ echo gettext("done.") . "\n";
+ }
+
+ unlock($filterlck);
+ return 0;
+}
+
+function filter_generate_scrubing() {
+ global $config, $FilterIflist;
+ $scrubrules = "";
+
+ if (isset($config['system']['maxmss_enable'])) {
+ $maxmss = 1400;
+ if (!empty($config['system']['maxmss'])) {
+ $maxmss = $config['system']['maxmss'];
+ }
+
+ $scrubrules .= "scrub from any to <vpn_networks> max-mss {$maxmss}\n";
+ $scrubrules .= "scrub from <vpn_networks> to any max-mss {$maxmss}\n";
+ }
+ /* disable scrub option */
+ foreach ($FilterIflist as $scrubif => $scrubcfg) {
+ if (isset($scrubcfg['virtual']) || empty($scrubcfg['descr'])) {
+ continue;
+ }
+ /* set up MSS clamping */
+ if (($scrubcfg['mss'] <> "") &&
+ (is_numeric($scrubcfg['mss'])) &&
+ ($scrubcfg['if'] != "pppoe") &&
+ ($scrubcfg['if'] != "pptp") &&
+ ($scrubif['if'] != "l2tp")) {
+ $mssclamp = "max-mss " . (intval($scrubcfg['mss'] - 40));
+ } else {
+ $mssclamp = "";
+ }
+ /* configure no-df for linux nfs and others */
+ if ($config['system']['scrubnodf']) {
+ $scrubnodf = "no-df";
+ } else {
+ $scrubnodf = "";
+ }
+ if ($config['system']['scrubrnid']) {
+ $scrubrnid = "random-id";
+ } else {
+ $scrubrnid = "";
+ }
+ if (!isset($config['system']['disablescrub'])) {
+ $scrubrules .= "scrub on \${$scrubcfg['descr']} all {$scrubnodf} {$scrubrnid} {$mssclamp} fragment reassemble\n"; // reassemble all directions
+ } else if (!empty($mssclamp)) {
+ $scrubrules .= "scrub on \${$scrubcfg['descr']} {$mssclamp}\n";
+ }
+ }
+ return $scrubrules;
+}
+
+function filter_generate_nested_alias($name, $alias, &$aliasnesting, &$aliasaddrnesting) {
+ global $aliastable, $filterdns;
+
+ $addresses = explode(" ", $alias);
+ $use_filterdns = false;
+ $finallist = "";
+ $builtlist = "";
+ $urltable_nesting = "";
+ $aliasnesting[$name] = $name;
+ $alias_type = alias_get_type($name);
+ foreach ($addresses as $address) {
+ if (empty($address)) {
+ continue;
+ }
+ $linelength = strlen($builtlist);
+ $tmpline = "";
+ if (is_alias($address)) {
+ if (alias_get_type($address) == 'urltable') {
+ // Feature#1603. For this type of alias we do not need to recursively call filter_generate_nested_alias. Just load IPs from the file.
+ $urltable_nesting = alias_expand_urltable($address);
+ if (!empty($urltable_nesting)) {
+ $urlfile_as_arr = file($urltable_nesting);
+ foreach ($urlfile_as_arr as $line) {
+ $address= rtrim($line);
+ if ((strlen($tmpline) + $linelength) > 4036) {
+ $finallist .= "{$tmpline} \\\n";
+ $tmpline = "";
+ }
+ $tmpline .= " {$address}";
+ }
+ }
+ }
+ /* We already expanded this alias so there is no necessity to do it again. */
+ else if (!isset($aliasnesting[$address])) {
+ $tmpline = filter_generate_nested_alias($name, $aliastable[$address], $aliasnesting, $aliasaddrnesting);
+ }
+ } else if (!isset($aliasaddrnesting[$address])) {
+ if (!is_ipaddr($address) && !is_subnet($address) && !((($alias_type == 'port') || ($alias_type == 'url_ports')) && (is_port($address) || is_portrange($address))) && is_hostname($address)) {
+ if (!isset($filterdns["{$address}{$name}"])) {
+ $use_filterdns = true;
+ $filterdns["{$address}{$name}"] = "pf {$address} {$name}\n";
+ }
+ continue;
+ }
+ $aliasaddrnesting[$address] = $address;
+ $tmpline = " {$address}";
+ }
+ if ((strlen($tmpline)+ $linelength) > 4036) {
+ $finallist .= "{$builtlist} \\\n";
+ $builtlist = "";
+ }
+ if (!empty($tmpline)) {
+ $builtlist .= " {$tmpline}";
+ }
+ }
+ $finallist .= $builtlist;
+
+ if ($use_filterdns === true && !empty($finallist)) {
+ foreach (explode(" ", $finallist) as $address) {
+ if (empty($address)) {
+ continue;
+ }
+ if ((is_ipaddr($address) || is_subnet($address)) && !isset($filterdns["{$address}{$name}"])) {
+ $filterdns["{$address}{$name}"] = "pf {$address} {$name}\n";
+ }
+ }
+ $finallist = '';
+ }
+
+ return $finallist;
+}
+
+function filter_expand_alias($alias_name) {
+ global $config;
+
+ if (isset($config['aliases']['alias'])) {
+ foreach ($config['aliases']['alias'] as $aliased) {
+ if ($aliased['name'] == $alias_name) {
+ $aliasnesting = array();
+ $aliasaddrnesting = array();
+ return filter_generate_nested_alias($aliased['name'], $aliased['address'], $aliasnesting, $aliasaddrnesting);
+ }
+ }
+ }
+}
+
+function filter_expand_alias_array($alias_name) {
+ $expansion = filter_expand_alias($alias_name);
+ return explode(" ", preg_replace('/\s+/', ' ', trim($expansion)));
+}
+
+function filter_generate_aliases() {
+ global $config, $FilterIflist, $after_filter_configure_run;
+
+ if (isset($config['system']['developerspew'])) {
+ $mt = microtime();
+ echo "filter_generate_aliases() being called $mt\n";
+ }
+
+ $alias = "#System aliases\n ";
+ $aliases = "loopback = \"{ lo0 }\"\n";
+
+ foreach ($FilterIflist as $if => $ifcfg) {
+ if (is_array($ifcfg[0])) {
+ if ($ifcfg[0]['if'] == 'pppoe') {
+ $aliases .= "{$ifcfg[0]['descr']} = \"{ {$ifcfg[0]['if']}";
+ $aliases .= " }\"\n";
+ }
+ } elseif (!empty($ifcfg['descr']) && !empty($ifcfg['if'])) {
+ if ($ifcfg['type6'] == '6rd') {
+ $aliases .= "{$ifcfg['descr']} = \"{ {$ifcfg['if']} {$if}_stf";
+ } else if ($ifcfg['type6'] == '6to4') {
+ $aliases .= "{$ifcfg['descr']} = \"{ {$ifcfg['if']} {$if}_stf";
+ } else {
+ $aliases .= "{$ifcfg['descr']} = \"{ {$ifcfg['if']}";
+
+ if ($ifcfg['type'] == 'pptp') {
+ foreach (get_parent_interface($ifcfg['if']) as $parent_if) {
+ if ($parent_if != $ifcfg['if']) {
+ $aliases .= " {$parent_if}";
+ }
+ }
+ }
+ }
+ $aliases .= " }\"\n";
+ }
+ }
+
+ $aliases .= "\n#SSH Lockout Table\n";
+ $aliases .= "table <sshlockout> persist\n";
+ $aliases .= "table <webConfiguratorlockout> persist\n";
+
+ $aliases .= "#Snort tables\n";
+ $aliases .= "table <snort2c>\n";
+ $aliases .= "table <virusprot>\n";
+ if (!file_exists("/etc/bogons") || !file_exists("/etc/bogonsv6")) {
+ conf_mount_rw();
+ if (!file_exists("/etc/bogons")) {
+ @file_put_contents("/etc/bogons", "");
+ }
+ if (!file_exists("/etc/bogonsv6")) {
+ @file_put_contents("/etc/bogonsv6", "");
+ }
+ conf_mount_ro();
+ }
+ $aliases .= "table <bogons> persist file \"/etc/bogons\"\n";
+ if (is_bogonsv6_used()) {
+ $aliases .= "table <bogonsv6> persist file \"/etc/bogonsv6\"\n";
+ }
+
+ $vpns_list = filter_get_vpns_list();
+ if ($vpns_list) {
+ $aliases .= "table <vpn_networks> { $vpns_list }\n";
+ }
+
+ /* add a Negate_networks table */
+ $aliases .= "table <negate_networks> ";
+ if ($vpns_list) {
+ $aliases .= "{ $vpns_list }";
+ }
+ $aliases .= "\n";
+
+ $aliases .= "\n# User Aliases \n";
+ /* Setup pf groups */
+ if (isset($config['aliases']['alias'])) {
+ foreach ($config['aliases']['alias'] as $aliased) {
+ $extralias = "";
+ $aliasnesting = array();
+ $aliasaddrnesting = array();
+ if (is_numericint($aliased['name'])) {
+ // skip aliases with numeric-only names. redmine #4289
+ file_notice("Filter_Reload", "Aliases with numeric-only names are not valid. Skipping alias " . $aliased['name']);
+ continue;
+ }
+ $addrlist = filter_generate_nested_alias($aliased['name'], $aliased['address'], $aliasnesting, $aliasaddrnesting);
+ switch ($aliased['type']) {
+ case "host":
+ case "network":
+ case "url":
+ $tableaddrs = "{$addrlist}{$extralias}";
+ if (empty($tableaddrs)) {
+ $aliases .= "table <{$aliased['name']}> persist\n";
+ if (empty($aliased['address'])) {
+ $after_filter_configure_run[] = "/sbin/pfctl -T flush -t " . escapeshellarg($aliased['name']);
+ }
+ } else {
+ $aliases .= "table <{$aliased['name']}> { {$addrlist}{$extralias} } \n";
+ }
+
+ $aliases .= "{$aliased['name']} = \"<{$aliased['name']}>\"\n";
+ break;
+ case "openvpn":
+ $openvpncfg = array();
+ if ($config['openvpn']['user']) {
+ /* XXX: Check if we have a correct ip? */
+ foreach ($config['openvpn']['user'] as $openvpn) {
+ $openvpncfg[$openvpn['name']] = $openvpn['ip'];
+ }
+ }
+ $vpn_lines = explode("\n", $addrlist);
+ foreach ($vpn_lines as $vpn_line) {
+ $vpn_address_split = explode(" ", $vpn_line);
+ foreach ($vpn_address_split as $vpnsplit) {
+ if (isset($openvpncfg[$vpnsplit])) {
+ $newaddress .= " ";
+ $newaddress .= $openvpn[$vpnsplit];
+ break;
+ }
+ }
+ }
+ $aliases .= "table <{$aliased['name']}> { {$newaddress}{$extralias} } \n";
+ $aliases .= "{$aliased['name']} = \"<{$aliased['name']}>\"\n";
+ break;
+ case "urltable":
+ $urlfn = alias_expand_urltable($aliased['name']);
+ if ($urlfn) {
+ $aliases .= "table <{$aliased['name']}> persist file \"{$urlfn}\"\n";
+ $aliases .= "{$aliased['name']} = \"<{$aliased['name']}>\"\n";
+ }
+ break;
+ case "urltable_ports":
+ // TODO: Change it when pf supports tables with ports
+ $urlfn = alias_expand_urltable($aliased['name']);
+ if ($urlfn) {
+ $aliases .= "{$aliased['name']} = \"{ " . preg_replace("/\n/", " ", file_get_contents($urlfn)) . " }\"\n";
+ }
+ break;
+ case "port":
+ case "url_ports":
+ $aliases .= "{$aliased['name']} = \"{ {$addrlist} }\"\n";
+ break;
+ default:
+ $aliases .= "{$aliased['name']} = \"{ {$aliased['address']}{$extralias} }\"\n";
+ break;
+ }
+ }
+ }
+ $result = "{$alias} \n";
+ $result .= "{$aliases}";
+
+ return $result;
+}
+
+function filter_generate_gateways() {
+ global $config, $g, $GatewaysList;
+
+ $rules = "# Gateways\n";
+
+ update_filter_reload_status(gettext("Creating gateway group item..."));
+
+ /* Lookup Gateways to be used in filter rules once */
+ $GatewaysList = return_gateways_array();
+ $GatewayGroupsList = return_gateway_groups_array();
+
+ if (is_array($GatewaysList)) {
+ foreach ($GatewaysList as $gwname => $gateway) {
+ $int = $gateway['interface'];
+ $gwip = $gateway['gateway'];
+ $route = "";
+ if (!is_ipaddr($gwip)) {
+ $gwip = get_interface_gateway($gateway['friendlyiface']);
+ }
+ if (is_ipaddr($gwip) && !empty($int) && !isset($gateway['force_down'])) {
+ $route = "route-to ( {$int} {$gwip} )";
+ }
+ if (($route === "") && isset($config['system']['skip_rules_gw_down'])) {
+ unset($GatewaysList[$gwname]);
+ } else {
+ $rules .= "GW{$gwname} = \" {$route} \"\n";
+ }
+ }
+ }
+
+ if (is_array($GatewayGroupsList)) {
+ foreach ($GatewayGroupsList as $gateway => $members) {
+ $route = "";
+ /* hey, that's not a group member! */
+ unset($members['ipprotocol']);
+ if (count($members) > 0) {
+ $foundlb = 0;
+ $routeto = "";
+ foreach ($members as $idx => $member) {
+ $int = $member['int'];
+ $gatewayip = $member['gwip'];
+ if (($int <> "") && is_ipaddr($gatewayip)) {
+ if ($g['debug']) {
+ log_error(sprintf(gettext('Setting up route with %1$s on %2$s'), $gatewayip, $int));
+ }
+ if ($member['weight'] > 1) {
+ $routeto .= str_repeat("( {$int} {$gatewayip} ) ", $member['weight']);
+ } else {
+ $routeto .= "( {$int} {$gatewayip} ) ";
+ }
+ $foundlb++;
+ } else {
+ log_error(sprintf(gettext("An error occurred while trying to find the interface got %s . The rule has not been added."), $gatewayip));
+ }
+ }
+ $route = "";
+ if ($foundlb > 0) {
+ $route = " route-to { {$routeto} } ";
+ if ($foundlb > 1) {
+ $route .= " round-robin ";
+ if (isset($config['system']['lb_use_sticky'])) {
+ $route .= " sticky-address ";
+ }
+ }
+ }
+ }
+ if (($route === "") && isset($config['system']['skip_rules_gw_down'])) {
+ unset($GatewayGroupsList[$gateway]);
+ } else {
+ $rules .= "GW{$gateway} = \" {$route} \"\n";
+ }
+ }
+ }
+
+ /* Create a global array to avoid errors on rulesets. */
+ $GatewaysList = $GatewaysList + $GatewayGroupsList;
+
+ $rules .= "\n";
+
+ return $rules;
+}
+
+/* returns space separated list of vpn subnets */
+function filter_get_vpns_list() {
+ global $config;
+
+ $vpns = "";
+ $vpns_arr = array();
+
+ /* ipsec */
+ if (isset($config['ipsec']['enable'])) {
+ if (is_array($config['ipsec']['phase2'])) {
+ foreach ($config['ipsec']['phase2'] as $ph2ent) {
+ if ((!$ph2ent['mobile']) && ($ph2ent['mode'] != 'transport')) {
+ if (!function_exists('ipsec_idinfo_to_cidr')) {
+ require_once("ipsec.inc");
+ }
+ if (!is_array($ph2ent['remoteid'])) {
+ continue;
+ }
+ $ph2ent['remoteid']['mode'] = $ph2ent['mode'];
+ $vpns_subnet = ipsec_idinfo_to_cidr($ph2ent['remoteid']);
+ if (!is_subnet($vpns_subnet) || $vpns_subnet == "0.0.0.0/0") {
+ continue;
+ }
+ $vpns_arr[] = $vpns_subnet;
+ }
+ }
+ }
+ }
+
+ /* openvpn */
+ foreach (array('client', 'server') as $type) {
+ if (is_array($config['openvpn']["openvpn-$type"])) {
+ foreach ($config['openvpn']["openvpn-$type"] as $settings) {
+ if (is_array($settings)) {
+ if (!isset($settings['disable'])) {
+ $remote_networks = explode(',', $settings['remote_network']);
+ foreach ($remote_networks as $remote_network) {
+ if (is_subnet($remote_network) && ($remote_network <> "0.0.0.0/0")) {
+ $vpns_arr[] = $remote_network;
+ }
+ }
+ if (is_subnet($settings['tunnel_network']) && $settings['tunnel_network'] <> "0.0.0.0/0") {
+ $vpns_arr[] = $settings['tunnel_network'];
+ }
+ }
+ }
+ }
+ }
+ }
+ /* pppoe */
+ if (is_array($config['pppoes']['pppoe'])) {
+ foreach ($config['pppoes']['pppoe'] as $pppoe) {
+ if ($pppoe['mode'] == "server") {
+ if (is_ipaddr($pppoe['remoteip'])) {
+ $pppoesub = gen_subnet($pppoe['remoteip'], $pppoe['pppoe_subnet']);
+ if (is_subnet($pppoesub)) {
+ $vpns_arr[] = $pppoesub;
+ }
+ }
+ }
+ }
+ }
+
+ if (!empty($vpns_arr)) {
+ $vpns = implode(" ", $vpns_arr);
+ }
+
+ return $vpns;
+}
+
+/* returns space separated list of directly connected networks
+ * optionally returns an array instead, including friendly interface and gateway (if applicable)
+ */
+function filter_get_direct_networks_list($returnsubnetsonly = true) {
+ global $config, $FilterIflist, $GatewaysList;
+ /* build list of directly connected interfaces and networks */
+ $networks = "";
+ $networks_arr = array();
+ if (empty($FilterIflist)) {
+ filter_generate_optcfg_array();
+ }
+ foreach ($FilterIflist as $ifent => $ifcfg) {
+ $subnet = "{$ifcfg['sa']}/{$ifcfg['sn']}";
+ if (is_subnet($subnet)) {
+ if ($returnsubnetsonly) {
+ $networks_arr[] = $subnet;
+ } else {
+ $networks_arr[] = array(
+ 'subnet' => $subnet,
+ 'if' => $ifent,
+ 'ip' => $ifcfg['ip']);
+ }
+ }
+ }
+ foreach (get_configured_ip_aliases_list(true) as $vip) {
+ $subnet = "{$vip['subnet']}/{$vip['subnet_bits']}";
+ if (is_subnet($subnet) && !(is_subnetv4($subnet) && $vip['subnet_bits'] == 32) && !(is_subnetv6($subnet) && $vip['subnet_bits'] == 128)) {
+ if (is_subnetv4($subnet)) {
+ $subnet = gen_subnet($vip['subnet'], $vip['subnet_bits']) . "/{$vip['subnet_bits']}";
+ } else if (is_subnetv6($subnet)) {
+ $subnet = gen_subnetv6($vip['subnet'], $vip['subnet_bits']) . "/{$vip['subnet_bits']}";
+ }
+ if ($returnsubnetsonly) {
+ $networks_arr[] = $subnet;
+ } else {
+ $networks_arr[] = array(
+ 'subnet' => $subnet,
+ 'if' => $vip['interface'],
+ 'ip' => $vip['subnet']);
+ }
+ }
+ }
+ foreach (get_staticroutes() as $netent) {
+ if (is_subnet($netent['network'])) {
+ if ($returnsubnetsonly) {
+ $networks_arr[] = $netent['network'];
+ } else if (isset($GatewaysList[$netent['gateway']])) {
+ $networks_arr[] = array(
+ 'subnet' => $netent['network'],
+ 'if' => $GatewaysList[$netent['gateway']]['friendlyiface'],
+ 'gateway' => $GatewaysList[$netent['gateway']]['gateway']);
+ }
+ }
+ }
+ if ($returnsubnetsonly) {
+ if (!empty($networks_arr)) {
+ $networks = implode(" ", $networks_arr);
+ }
+ return $networks;
+ } else {
+ return $networks_arr;
+ }
+}
+
+function filter_generate_optcfg_array() {
+ global $config, $FilterIflist;
+ if (isset($config['system']['developerspew'])) {
+ $mt = microtime();
+ echo "filter_generate_optcfg_array() being called $mt\n";
+ }
+
+ read_layer7_config();
+ /* if list */
+ $iflist = get_configured_interface_with_descr();
+ foreach ($iflist as $if => $ifdetail) {
+ $oc = $config['interfaces'][$if];
+ $oic = array();
+ $oic['if'] = get_real_interface($if);
+ if (!does_interface_exist($oic['if'])) {
+ continue;
+ }
+ $oic['ifv6'] = get_real_interface($if, "inet6");
+ $oic['ip'] = get_interface_ip($if);
+ $oic['ipv6'] = get_interface_ipv6($if);
+ if (!is_ipaddrv4($oc['ipaddr']) && !empty($oc['ipaddr'])) {
+ $oic['type'] = $oc['ipaddr'];
+ }
+ if (!is_ipaddrv6($oc['ipaddrv6']) && !empty($oc['ipaddrv6'])) {
+ $oic['type6'] = $oc['ipaddrv6'];
+ }
+ if (!empty($oc['track6-interface'])) {
+ $oic['track6-interface'] = $oc['track6-interface'];
+ }
+ $oic['sn'] = get_interface_subnet($if);
+ $oic['snv6'] = get_interface_subnetv6($if);
+ $oic['mtu'] = empty($oc['mtu']) ? 1500 : $oc['mtu'];
+ $oic['mss'] = empty($oc['mss']) ? '' : $oc['mss'];
+ $oic['descr'] = $ifdetail;
+ $oic['sa'] = gen_subnet($oic['ip'], $oic['sn']);
+ $oic['sav6'] = gen_subnetv6($oic['ipv6'], $oic['snv6']);
+ $oic['nonat'] = $oc['nonat'];
+ $oic['alias-address'] = $oc['alias-address'];
+ $oic['alias-subnet'] = $oc['alias-subnet'];
+ $oic['gateway'] = $oc['gateway'];
+ $oic['gatewayv6'] = $oc['gatewayv6'];
+ $oic['spoofcheck'] = "yes";
+ $oic['bridge'] = link_interface_to_bridge($if);
+ $vips = link_interface_to_vips($if);
+ if (!empty($vips)) {
+ foreach ($vips as $vipidx => $vip) {
+ if (is_ipaddrv4($vip['subnet'])) {
+ if (!is_array($oic['vips'])) {
+ $oic['vips'] = array();
+ }
+ $oic['vips'][$vipidx]['ip'] = $vip['subnet'];
+ if (empty($vip['subnet_bits'])) {
+ $oic['vips'][$vipidx]['sn'] = 32;
+ } else {
+ $oic['vips'][$vipidx]['sn'] = $vip['subnet_bits'];
+ }
+ } else if (is_ipaddrv6($vip['subnet'])) {
+ if (!is_array($oic['vips6'])) {
+ $oic['vips6'] = array();
+ }
+ $oic['vips6'][$vipidx]['ip'] = $vip['subnet'];
+ if (empty($vip['subnet_bits'])) {
+ $oic['vips6'][$vipidx]['sn'] = 128;
+ } else {
+ $oic['vips6'][$vipidx]['sn'] = $vip['subnet_bits'];
+ }
+ }
+ }
+ }
+ unset($vips);
+ $FilterIflist[$if] = $oic;
+ }
+
+ if ($config['pptpd']['mode'] == "server" || $config['pptpd']['mode'] == "redir") {
+ $oic = array();
+ $oic['if'] = 'pptp';
+ $oic['descr'] = 'pptp';
+ $oic['ip'] = $config['pptpd']['localip'];
+ $oic['sa'] = $config['pptpd']['remoteip'];
+ $oic['mode'] = $config['pptpd']['mode'];
+ $oic['virtual'] = true;
+ if ($config['pptpd']['pptp_subnet'] <> "") {
+ $oic['sn'] = $config['pptpd']['pptp_subnet'];
+ } else {
+ $oic['sn'] = "32";
+ }
+ $FilterIflist['pptp'] = $oic;
+ }
+ if ($config['l2tp']['mode'] == "server") {
+ $oic = array();
+ $oic['if'] = 'l2tp';
+ $oic['descr'] = 'L2TP';
+ $oic['ip'] = $config['l2tp']['localip'];
+ $oic['sa'] = $config['l2tp']['remoteip'];
+ if ($config['l2tp']['l2tp_subnet'] <> "") {
+ $oic['sn'] = $config['l2tp']['l2tp_subnet'];
+ } else {
+ $oic['sn'] = "32";
+ }
+ $oic['mode'] = $config['l2tp']['mode'];
+ $oic['virtual'] = true;
+ $FilterIflist['l2tp'] = $oic;
+ }
+ if (is_array($config['pppoes']['pppoe']) && (count($config['pppoes']['pppoe']) > 0)) {
+ $pppoeifs = array();
+ foreach ($config['pppoes']['pppoe'] as $pppoe) {
+ if ($pppoe['mode'] == "server") {
+ $oic = array();
+ $oic['if'] = 'pppoe';
+ $oic['descr'] = 'pppoe';
+ $oic['ip'] = $pppoe['localip'];
+ $oic['sa'] = $pppoe['remoteip'];
+ $oic['mode'] = $pppoe['mode'];
+ $oic['virtual'] = true;
+ if ($pppoe['pppoe_subnet'] <> "") {
+ $oic['sn'] = $pppoe['pppoe_subnet'];
+ } else {
+ $oic['sn'] = "32";
+ }
+ $pppoeifs[] = $oic;
+ }
+ }
+ if (count($pppoeifs)) {
+ $FilterIflist['pppoe'] = $pppoeifs;
+ }
+ }
+ /* add ipsec interfaces */
+ if (isset($config['ipsec']['enable']) || isset($config['ipsec']['client']['enable'])) {
+ $oic = array();
+ $oic['if'] = 'enc0';
+ $oic['descr'] = 'IPsec';
+ $oic['type'] = "none";
+ $oic['virtual'] = true;
+ $FilterIflist['enc0'] = $oic;
+ }
+ /* add openvpn interfaces */
+ if ($config['openvpn']['openvpn-server'] || $config['openvpn']['openvpn-client']) {
+ $oic = array();
+ $oic['if'] = "openvpn";
+ $oic['descr'] = 'OpenVPN';
+ $oic['type'] = "none";
+ $oic['virtual'] = true;
+ $FilterIflist['openvpn'] = $oic;
+ }
+ /* add interface groups */
+ if (is_array($config['ifgroups']['ifgroupentry'])) {
+ foreach ($config['ifgroups']['ifgroupentry'] as $ifgen) {
+ $oc = array();
+ $oc['if'] = $ifgen['ifname'];
+ $oc['descr'] = $ifgen['ifname'];
+ $oc['virtual'] = true;
+ $FilterIflist[$ifgen['ifname']] = $oc;
+ }
+ }
+}
+
+function filter_flush_nat_table() {
+ global $config, $g;
+ if (isset($config['system']['developerspew'])) {
+ $mt = microtime();
+ echo "filter_flush_nat_table() being called $mt\n";
+ }
+ return mwexec("/sbin/pfctl -F nat");
+}
+
+function filter_flush_state_table() {
+ return mwexec("/sbin/pfctl -F state");
+}
+
+function filter_get_reflection_interfaces($natif = "") {
+ global $FilterIflist;
+
+ $nat_if_list = array();
+
+ foreach ($FilterIflist as $ifent => $ifname) {
+ if ($ifname['if'] == $natif) {
+ continue;
+ }
+
+ /* Do not add reflection redirects for interfaces with gateways */
+ if (interface_has_gateway($ifent)) {
+ continue;
+ }
+
+ $nat_if_list[] = $ifname['if'];
+ }
+
+ return $nat_if_list;
+}
+
+function filter_generate_reflection_nat($rule, &$route_table, $nat_ifs, $protocol, $target, $target_ip, $target_subnet = "") {
+ global $config, $FilterIflist;
+
+ if (!isset($config['system']['enablenatreflectionhelper'])) {
+ return "";
+ }
+
+ // Initialize natrules holder string
+ $natrules = "";
+
+ update_filter_reload_status(sprintf(gettext("Creating reflection NAT rule for %s..."), $rule['descr']));
+
+ /* TODO: Add this option to port forwards page. */
+ if (isset($rule['staticnatport'])) {
+ $static_port = " static-port";
+ } else {
+ $static_port = " port 1024:65535";
+ }
+
+ if (!empty($protocol)) {
+ $protocol_text = " proto {$protocol}";
+ } else {
+ $protocol_text = "";
+ }
+
+ if (empty($target_subnet) || !is_numeric($target_subnet)) {
+ $target_subnet = 32;
+ }
+
+ if (!is_array($route_table)) {
+ /* get a simulated IPv4-only route table based on the config */
+ $route_table = filter_get_direct_networks_list(false);
+ foreach ($route_table as $rt_key => $rt_ent) {
+ if (!is_subnetv4($rt_ent['subnet'])) {
+ unset($route_table[$rt_key]);
+ }
+ if (isset($route_table[$rt_key]) && isset($FilterIflist[$rt_ent['if']]['if'])) {
+ $route_table[$rt_key]['if'] = $FilterIflist[$rt_ent['if']]['if'];
+ }
+ }
+ }
+
+ /* Check if the target is accessed through a static route */
+ foreach ($route_table as $route) {
+ if (isset($route['gateway']) && is_ipaddr($route['gateway'])) {
+ $subnet_split = explode("/", $route['subnet']);
+ if (in_array($route['if'], $nat_ifs) && check_subnets_overlap($target_ip, $target_subnet, $subnet_split[0], $subnet_split[1])) {
+ $target_ip = $route['gateway'];
+ $target_subnet = 32;
+ break;
+ }
+ }
+ }
+
+ /* Search for matching subnets in the routing table */
+ foreach ($route_table as $route) {
+ $subnet = $route['subnet'];
+ $subnet_split = explode("/", $subnet);
+ $subnet_if = $route['if'];
+ /* Blacklist invalid "from" sources since they can be picked up accidentally and cause rule errors. */
+ $no_reflect_from = array("l2tp");
+ if (in_array($subnet_if, $nat_ifs) && check_subnets_overlap($target_ip, $target_subnet, $subnet_split[0], $subnet_split[1])) {
+ $ifsubnet_ip = "";
+ /* Find interface IP to use for NAT */
+ foreach ($route_table as $ifnetwork) {
+ if (isset($ifnetwork['ip']) && is_ipaddr($ifnetwork['ip']) && $ifnetwork['if'] == $subnet_if && ip_in_subnet($ifnetwork['ip'], $subnet)) {
+ $ifsubnet_ip = $ifnetwork['ip'];
+ break;
+ }
+ }
+ if(!empty($ifsubnet_ip) && !in_array($subnet, $no_reflect_from)) {
+ $subnets = array($subnet);
+ /* Find static routes that also need to be referenced in the NAT rule */
+ foreach ($route_table as $rtentry) {
+ if (isset($rtentry['gateway']) && is_ipaddr($rtentry['gateway']) && $rtentry['if'] == $subnet_if && ip_in_subnet($rtentry['gateway'], $subnet)) {
+ $subnets[] = $rtentry['subnet'];
+ }
+ }
+ if (count($subnets) > 1) {
+ $subnet = "{ " . implode(" ", $subnets) . " }";
+ }
+ $natrules .= "no nat on {$subnet_if}{$protocol_text} from {$subnet_if} to {$target}\n";
+ $natrules .= "nat on {$subnet_if}{$protocol_text} from {$subnet} to {$target} -> {$ifsubnet_ip}{$static_port}\n";
+ }
+ }
+ }
+
+ if (!empty($natrules)) {
+ $natrules .= "\n";
+ }
+
+ return $natrules;
+}
+
+function filter_generate_reflection_proxy($rule, $nordr, $rdr_ifs, $srcaddr, $dstaddr_port, &$starting_localhost_port, &$reflection_txt) {
+ global $FilterIflist, $config;
+
+ // Initialize natrules holder string
+ $natrules = "";
+ $reflection_txt = array();
+
+ if (!empty($rdr_ifs)) {
+ if ($config['system']['reflectiontimeout']) {
+ $reflectiontimeout = $config['system']['reflectiontimeout'];
+ } else {
+ $reflectiontimeout = "2000";
+ }
+
+ update_filter_reload_status(sprintf(gettext("Creating reflection rule for %s..."), $rule['descr']));
+
+ $rdr_if_list = implode(" ", $rdr_ifs);
+ if (count($rdr_ifs) > 1) {
+ $rdr_if_list = "{ {$rdr_if_list} }";
+ }
+
+ $natrules .= "\n# Reflection redirects\n";
+
+ $localport = $rule['local-port'];
+ if (!empty($localport) && is_alias($localport)) {
+ $localport = filter_expand_alias($localport);
+ $localport = explode(" ", trim($localport));
+ // The translation port for rdr, when specified, does not support more than one port or range.
+ // Emulating for behavior consistent with the original port forward.
+ $localport = $localport[0];
+ }
+
+ if (is_alias($rule['destination']['port'])) {
+ if (empty($localport) || $rule['destination']['port'] == $rule['local-port']) {
+ $dstport = filter_expand_alias($rule['destination']['port']);
+ $dstport = array_filter(explode(" ", trim($dstport)));
+ $localport = "";
+ } else if (!empty($localport)) {
+ $dstport = array($localport);
+ }
+ } else {
+ $dstport = array(str_replace("-", ":", $rule['destination']['port']));
+ $dstport_split = explode(":", $dstport[0]);
+
+ if (!empty($localport) && $dstport_split[0] != $rule['local-port']) {
+ if (!is_alias($rule['local-port']) && $dstport_split[1] && $dstport_split[0] != $dstport_split[1]) {
+ $localendport = $localport + ($dstport_split[1] - $dstport_split[0]);
+ $localport .= ":$localendport";
+ }
+
+ $dstport = array($localport);
+ } else {
+ $localport = "";
+ }
+ }
+
+ $dstaddr = explode(" ", $dstaddr_port);
+ if ($dstaddr[2]) {
+ $rflctintrange = array_pop($dstaddr);
+ array_pop($dstaddr);
+ } else {
+ return "";
+ }
+ $dstaddr = implode(" ", $dstaddr);
+ if (empty($dstaddr) || trim($dstaddr) == "0.0.0.0" || strtolower(trim($dstaddr)) == "port") {
+ return "";
+ }
+
+ if (isset($rule['destination']['any'])) {
+ if (!$rule['interface']) {
+ $natif = "wan";
+ } else {
+ $natif = $rule['interface'];
+ }
+
+ if (!isset($FilterIflist[$natif])) {
+ return "";
+ }
+ if (is_ipaddr($FilterIflist[$natif]['ip'])) {
+ $dstaddr = $FilterIflist[$natif]['ip'];
+ } else {
+ return "";
+ }
+
+ if (!empty($FilterIflist[$natif]['sn'])) {
+ $dstaddr = gen_subnet($dstaddr, $FilterIflist[$natif]['sn']) . '/' . $FilterIflist[$natif]['sn'];
+ }
+ }
+
+ switch ($rule['protocol']) {
+ case "tcp/udp":
+ $protocol = "{ tcp udp }";
+ $reflect_protos = array('tcp', 'udp');
+ break;
+ case "tcp":
+ case "udp":
+ $protocol = $rule['protocol'];
+ $reflect_protos = array($rule['protocol']);
+ break;
+ default:
+ return "";
+ break;
+ }
+
+ if (!empty($nordr)) {
+ $natrules .= "no rdr on {$rdr_if_list} proto {$protocol} from {$srcaddr} to {$dstaddr} port {$rflctintrange}\n";
+ return $natrules;
+ }
+
+ if (is_alias($rule['target'])) {
+ $target = filter_expand_alias($rule['target']);
+ } else if (is_ipaddr($rule['target'])) {
+ $target = $rule['target'];
+ } else if (is_ipaddr($FilterIflist[$rule['target']]['ip'])) {
+ $target = $FilterIflist[$rule['target']]['ip'];
+ } else {
+ return "";
+ }
+ $starting_localhost_port_tmp = $starting_localhost_port;
+ $toomanyports = false;
+ /* only install reflection rules for < 19991 items */
+ foreach ($dstport as $loc_pt) {
+ if ($starting_localhost_port < 19991) {
+ $toadd_array = array();
+ $inetdport = $starting_localhost_port;
+ $rflctrange = $starting_localhost_port;
+
+ $loc_pt = explode(":", $loc_pt);
+ if ($loc_pt[1] && $loc_pt[1] > $loc_pt[0]) {
+ $delta = $loc_pt[1] - $loc_pt[0];
+ } else {
+ $delta = 0;
+ }
+
+ if (($inetdport + $delta + 1) - $starting_localhost_port_tmp > 500) {
+ log_error("Not installing NAT reflection rules for a port range > 500");
+ $inetdport = $starting_localhost_port;
+ $toadd_array = array();
+ $toomanyports = true;
+ break;
+ } else if (($inetdport + $delta) > 19990) {
+ log_error("Installing partial NAT reflection rules. Maximum 1,000 reached.");
+ $delta = 19990 - $inetdport;
+ $loc_pt[1] = $loc_pt[0] + $delta;
+ if ($delta == 0) {
+ unset($loc_pt[1]);
+ }
+ $toomanyports = true;
+
+ if (!empty($localport)) {
+ if (is_alias($rule['destination']['port'])) {
+ $rflctintrange = alias_expand($rule['destination']['port']);
+ } else {
+ if ($dstport_split[1]) {
+ $dstport_split[1] = $dstport_split[0] + $inetdport + $delta - $starting_localhost_port;
+ }
+ $rflctintrange = implode(":", $dstport_split);
+ }
+ }
+ }
+
+ if (empty($localport)) {
+ $rflctintrange = implode(":", $loc_pt);
+ }
+ if ($inetdport + $delta > $starting_localhost_port) {
+ $rflctrange .= ":" . ($inetdport + $delta);
+ }
+ $starting_localhost_port = $inetdport + $delta + 1;
+ $toadd_array = array_merge($toadd_array, range($loc_pt[0], $loc_pt[0] + $delta));
+
+ if (!empty($toadd_array)) {
+ $rtarget = explode(" ", trim($target));
+ foreach ($toadd_array as $tda) {
+ if (empty($tda)) {
+ continue;
+ }
+ foreach ($reflect_protos as $reflect_proto) {
+ if ($reflect_proto == "udp") {
+ $socktype = "dgram";
+ $dash_u = "-u ";
+ $wait = "wait\t";
+ } else {
+ $socktype = "stream";
+ $dash_u = "";
+ $wait = "nowait/0";
+ }
+ foreach ($rtarget as $targip) {
+ if (empty($targip)) {
+ continue;
+ }
+ $reflection_txt[] = "{$inetdport}\t{$socktype}\t{$reflect_proto}\t{$wait}\tnobody\t/usr/bin/nc\tnc {$dash_u}-w {$reflectiontimeout} {$targip} {$tda}\n";
+ }
+ }
+ $inetdport++;
+ }
+ $natrules .= "rdr on {$rdr_if_list} proto {$protocol} from {$srcaddr} to {$dstaddr} port {$rflctintrange} tag PFREFLECT -> 127.0.0.1 port {$rflctrange}\n";
+ }
+ }
+
+ if ($toomanyports) {
+ break;
+ }
+ }
+
+ $reflection_txt = array_unique($reflection_txt);
+ }
+
+ return $natrules;
+}
+
+function filter_nat_rules_automatic_tonathosts($with_descr = false) {
+ global $config, $FilterIflist, $GatewaysList;
+
+ $tonathosts = array("127.0.0.0/8");
+ $descriptions = array(gettext("localhost"));
+
+ foreach (get_staticroutes() as $route) {
+ $netip = explode("/", $route['network']);
+ if (isset($GatewaysList[$route['gateway']])) {
+ $gateway =& $GatewaysList[$route['gateway']];
+ if (!interface_has_gateway($gateway['interface']) && is_private_ip($netip[0])) {
+ $tonathosts[] = $route['network'];
+ $descriptions[] = gettext("static route");
+ }
+ }
+ }
+
+ /* create outbound nat entries for all local networks */
+ foreach ($FilterIflist as $ocname => $oc) {
+ if (interface_has_gateway($ocname)) {
+ continue;
+ }
+ if (is_ipaddr($oc['alias-address'])) {
+ $tonathosts[] = "{$oc['alias-address']}/{$oc['alias-subnet']}";
+ $descriptions[] = $oc['descr'] . " " . gettext("DHCP alias address");
+ }
+ if ($oc['sa']) {
+ $tonathosts[] = "{$oc['sa']}/{$oc['sn']}";
+ $descriptions[] = $oc['descr'];
+ if (isset($oc['vips']) && is_array($oc['vips'])) {
+ $if_subnets = array("{$oc['sa']}/{$oc['sn']}");
+ foreach ($oc['vips'] as $vip) {
+ if (!is_ipaddrv4($vip['ip'])) {
+ continue;
+ }
+
+ foreach ($if_subnets as $subnet) {
+ if (ip_in_subnet($vip['ip'], $subnet)) {
+ continue 2;
+ }
+ }
+
+ $network = gen_subnet($vip['ip'], $vip['sn']);
+ array_unshift($tonathosts, $network . '/' . $vip['sn']);
+ array_unshift($descriptions, "Virtual IP ({$oc['descr']})");
+ $if_subnets[] = $network . '/' . $vip['sn'];
+ unset($network);
+ }
+ unset($if_subnets);
+ }
+ }
+ }
+
+ /* PPTP subnet */
+ if (($config['pptpd']['mode'] == "server") && is_private_ip($config['pptpd']['remoteip'])) {
+ if (isset($config['pptpd']['n_pptp_units']) && is_numeric($config['pptpd']['n_pptp_units'])) {
+ $pptp_subnets = ip_range_to_subnet_array($config['pptpd']['remoteip'],
+ long2ip32(ip2long($config['pptpd']['remoteip'])+($config['pptpd']['n_pptp_units']-1)));
+ } else {
+ $pptp_subnets = ip_range_to_subnet_array($config['pptpd']['remoteip'],
+ long2ip32(ip2long($config['pptpd']['remoteip'])));
+ }
+
+ foreach ($pptp_subnets as $subnet) {
+ $tonathosts[] = $subnet;
+ $descriptions[] = gettext("PPTP server");
+ }
+ }
+
+ /* PPPoE subnet */
+ if (is_array($FilterIflist['pppoe'])) {
+ foreach ($FilterIflist['pppoe'] as $pppoe) {
+ if (is_private_ip($pppoe['ip'])) {
+ $tonathosts[] = "{$pppoe['sa']}/{$pppoe['sn']}";
+ $descriptions[] = gettext("PPPoE server");
+ }
+ }
+ }
+
+ /* L2TP subnet */
+ if (isset($FilterIflist['l2tp']) && $FilterIflist['l2tp']['mode'] == "server") {
+ $l2tp_sa = $FilterIflist['l2tp']['sa'];
+ $l2tp_sn = $FilterIflist['l2tp']['sn'];
+ if (is_private_ip($l2tp_sa) && !empty($l2tp_sn)) {
+ $tonathosts[] = "{$l2tp_sa}/{$l2tp_sn}";
+ $descriptions[] = gettext("L2TP server");
+ }
+ }
+
+ /* add openvpn interfaces */
+ if (is_array($config['openvpn']['openvpn-server'])) {
+ foreach ($config['openvpn']['openvpn-server'] as $ovpnsrv) {
+ if (!isset($ovpnsrv['disable']) && !empty($ovpnsrv['tunnel_network'])) {
+ $tonathosts[] = $ovpnsrv['tunnel_network'];
+ $descriptions[] = gettext("OpenVPN server");
+ }
+ }
+ }
+
+ if (is_array($config['openvpn']['openvpn-client'])) {
+ foreach ($config['openvpn']['openvpn-client'] as $ovpncli) {
+ if (!isset($ovpncli['disable']) && !empty($ovpncli['tunnel_network'])) {
+ $tonathosts[] = $ovpncli['tunnel_network'];
+ $descriptions[] = gettext("OpenVPN client");
+ }
+ }
+ }
+
+ /* IPsec mode_cfg subnet */
+ if ((isset($config['ipsec']['client']['enable'])) &&
+ (!empty($config['ipsec']['client']['pool_address'])) &&
+ (!empty($config['ipsec']['client']['pool_netbits']))) {
+ $tonathosts[] = "{$config['ipsec']['client']['pool_address']}/{$config['ipsec']['client']['pool_netbits']}";
+ $descriptions[] = gettext("IPsec client");
+ }
+
+ if ($with_descr) {
+ $combined = array();
+ foreach ($tonathosts as $idx => $subnet) {
+ $combined[] = array(
+ "subnet" => $subnet,
+ "descr" => $descriptions[$idx]);
+ }
+
+ return $combined;
+ } else {
+ return $tonathosts;
+ }
+}
+
+function filter_nat_rules_outbound_automatic($src) {
+ global $config, $FilterIflist;
+
+ $rules = array();
+ foreach ($FilterIflist as $if => $ifcfg) {
+ if (substr($ifcfg['if'], 0, 4) == "ovpn") {
+ continue;
+ }
+ if (!interface_has_gateway($if)) {
+ continue;
+ }
+
+ $natent = array();
+ $natent['interface'] = $if;
+ $natent['source']['network'] = $src;
+ $natent['dstport'] = "500";
+ $natent['target'] = "";
+ $natent['destination']['any'] = true;
+ $natent['staticnatport'] = true;
+ $natent['descr'] = gettext('Auto created rule for ISAKMP');
+ $rules[] = $natent;
+
+ $natent = array();
+ $natent['interface'] = $if;
+ $natent['source']['network'] = $src;
+ $natent['sourceport'] = "";
+ $natent['target'] = "";
+ $natent['destination']['any'] = true;
+ $natent['natport'] = "";
+ $natent['descr'] = gettext('Auto created rule');
+ if (isset($ifcfg['nonat'])) {
+ $natent['nonat'] = true;
+ }
+ $rules[] = $natent;
+ }
+
+ return $rules;
+}
+
+/* Generate a 'nat on' or 'no nat on' rule for given interface */
+function filter_nat_rules_generate_if ($if, $src = "any", $srcport = "", $dst = "any", $dstport = "", $natip = "", $natport = "", $nonat = false, $staticnatport = false, $proto = "", $poolopts = "") {
+ global $config, $FilterIflist;
+ /* XXX: billm - any idea if this code is needed? */
+ if ($src == "/32" || $src{0} == "/") {
+ return "# src incorrectly specified\n";
+ }
+ if ($natip != "") {
+ if (is_subnet($natip)) {
+ $tgt = $natip;
+ } elseif (is_alias($natip)) {
+ $tgt = "\${$natip}";
+ } else {
+ $tgt = "{$natip}/32";
+ }
+ } else {
+ $natip = get_interface_ip($if);
+ if (is_ipaddr($natip)) {
+ $tgt = "{$natip}/32";
+ } else {
+ $tgt = "(" . $FilterIflist[$if]['if'] . ")";
+ }
+ }
+ /* Add the protocol, if defined */
+ if (!empty($proto) && $proto != "any") {
+ if ($proto == "tcp/udp") {
+ $protocol = " proto { tcp udp }";
+ } else {
+ $protocol = " proto {$proto}";
+ }
+ } else {
+ $protocol = "";
+ }
+ /* Set tgt for IPv6 */
+ if ($proto == "ipv6") {
+ $natip = get_interface_ipv6($if);
+ if (is_ipaddrv6($natip)) {
+ $tgt = "{$natip}/128";
+ }
+ }
+ /* Add the hard set source port (useful for ISAKMP) */
+ if ($natport != "") {
+ $tgt .= " port {$natport}";
+ }
+ /* sometimes this gets called with "" instead of a value */
+ if ($src == "") {
+ $src = "any";
+ }
+ /* Match on this source port */
+ if ($srcport != "") {
+ $srcportexpand = alias_expand($srcport);
+ if (!$srcportexpand) {
+ $srcportexpand = $srcport;
+ }
+ $src .= " port {$srcportexpand}";
+ }
+ /* sometimes this gets called with "" instead of a value */
+ if ($dst == "") {
+ $dst = "any";
+ }
+ /* Match on this dest port */
+ if ($dstport != "") {
+ $dstportexpand = alias_expand($dstport);
+ if (!$dstportexpand) {
+ $dstportexpand = $dstport;
+ }
+ $dst .= " port {$dstportexpand}";
+ }
+ /* outgoing static-port option, hamachi, Grandstream, VOIP, etc */
+ $staticnatport_txt = "";
+ if ($staticnatport) {
+ $staticnatport_txt = "static-port";
+ } elseif (!$natport) {
+ $tgt .= " port 1024:65535"; // set source port range
+ }
+ /* Allow for negating NAT entries */
+ if ($nonat) {
+ $nat = "no nat";
+ $target = "";
+ $staticnatport_txt = "";
+ $poolopts = "";
+ } else {
+ $nat = "nat";
+ $target = "-> {$tgt}";
+ }
+ $if_friendly = $FilterIflist[$if]['descr'];
+ /* Put all the pieces together */
+ if ($if_friendly) {
+ $natrule = "{$nat} on \${$if_friendly} {$protocol} from {$src} to {$dst} {$target} {$poolopts} {$staticnatport_txt}\n";
+ } else {
+ $natrule .= "# Could not convert {$if} to friendly name(alias)\n";
+ }
+ return $natrule;
+}
+
+function filter_nat_rules_generate() {
+ global $config, $g, $after_filter_configure_run, $FilterIflist, $GatewaysList, $aliases;
+
+ $natrules = "no nat proto carp\n";
+ $natrules .= "no rdr proto carp\n";
+ $natrules .= "nat-anchor \"natearly/*\"\n";
+
+ $natrules .= "nat-anchor \"natrules/*\"\n\n";
+ update_filter_reload_status(gettext("Creating 1:1 rules..."));
+
+ $reflection_txt = "";
+ $route_table = "";
+
+ /* any 1:1 mappings? */
+ if (is_array($config['nat']['onetoone'])) {
+ foreach ($config['nat']['onetoone'] as $rule) {
+ if (isset($rule['disabled'])) {
+ continue;
+ }
+
+ $sn = "";
+ $sn1 = "";
+ $target = alias_expand($rule['external']);
+ if (!$target) {
+ $natrules .= "# Unresolvable alias {$rule['target']}\n";
+ continue; /* unresolvable alias */
+ }
+
+ if (!$rule['interface']) {
+ $natif = "wan";
+ } else {
+ $natif = $rule['interface'];
+ }
+ if (!isset($FilterIflist[$natif])) {
+ continue;
+ }
+
+ $srcaddr = filter_generate_address($rule, 'source');
+ $dstaddr = filter_generate_address($rule, 'destination');
+ if (!$dstaddr) {
+ $dstaddr = $FilterIflist[$natif]['ip'];
+ }
+
+ $srcaddr = trim($srcaddr);
+ $dstaddr = trim($dstaddr);
+
+ $tmp = explode('/', $srcaddr);
+ $srcip = $tmp[0];
+ if (!empty($tmp[1]) && is_numeric($tmp[1])) {
+ $sn = $tmp[1];
+ $sn1 = "/{$sn}";
+ }
+
+ $natif = $FilterIflist[$natif]['if'];
+
+ /*
+ * If reflection is enabled, turn on extra redirections
+ * for this rule by adding other interfaces to an rdr rule.
+ */
+ if ((isset($config['system']['enablebinatreflection']) || $rule['natreflection'] == "enable") &&
+ ($rule['natreflection'] != "disable")) {
+ $nat_if_list = filter_get_reflection_interfaces($natif);
+ } else {
+ $nat_if_list = array();
+ }
+
+ $natrules .= "binat on {$natif} from {$srcaddr} to {$dstaddr} -> {$target}{$sn1}\n";
+ if (!empty($nat_if_list)) {
+ $binat_if_list = implode(" ", $nat_if_list);
+ $binat_if_list = "{ {$binat_if_list} }";
+ $reflection_txt .= "rdr on {$binat_if_list} from {$dstaddr} to {$target}{$sn1} -> {$srcaddr} bitmask\n";
+ }
+
+ $nat_if_list = array_merge(array($natif), $nat_if_list);
+ $reflection_txt .= filter_generate_reflection_nat($rule, $route_table, $nat_if_list, "", $srcaddr, $srcip, $sn);
+ }
+ }
+
+ /* Add binat rules for Network Prefix translation */
+ if (is_array($config['nat']['npt'])) {
+ foreach ($config['nat']['npt'] as $rule) {
+ if (isset($rule['disabled'])) {
+ continue;
+ }
+
+ if (!$rule['interface']) {
+ $natif = "wan";
+ } else {
+ $natif = $rule['interface'];
+ }
+ if (!isset($FilterIflist[$natif])) {
+ continue;
+ }
+
+ $srcaddr = filter_generate_address($rule, 'source');
+ $dstaddr = filter_generate_address($rule, 'destination');
+
+ $srcaddr = trim($srcaddr);
+ $dstaddr = trim($dstaddr);
+
+ $natif = $FilterIflist[$natif]['descr'];
+
+ $natrules .= "binat on \${$natif} from {$srcaddr} to any -> {$dstaddr}\n";
+ $natrules .= "binat on \${$natif} from any to {$dstaddr} -> {$srcaddr}\n";
+
+ }
+ }
+
+ /* ipsec nat */
+ if (is_array($config['ipsec']) && isset($config['ipsec']['enable'])) {
+ if (is_array($config['ipsec']['phase2'])) {
+ foreach ($config['ipsec']['phase2'] as $ph2ent) {
+ if ($ph2ent['mode'] != 'transport' && !empty($ph2ent['natlocalid'])) {
+ if (!function_exists('ipsec_idinfo_to_cidr')) {
+ require_once("ipsec.inc");
+ }
+ if (!is_array($ph2ent['localid'])) {
+ $ph2ent['localid'] = array();
+ }
+ $ph2ent['localid']['mode'] = $ph2ent['mode'];
+ $local_subnet = ipsec_idinfo_to_cidr($ph2ent['localid']);
+ if (empty($local_subnet) || $local_subnet == "0.0.0.0/0") {
+ continue;
+ }
+ if (!is_subnet($local_subnet) && !is_ipaddr($local_subnet)) {
+ continue;
+ }
+ if (!is_array($ph2ent['natlocalid'])) {
+ $ph2ent['natlocalid'] = array();
+ }
+ $ph2ent['natlocalid']['mode'] = $ph2ent['mode'];
+ $natlocal_subnet = ipsec_idinfo_to_cidr($ph2ent['natlocalid']);
+ if (empty($natlocal_subnet) || $natlocal_subnet == "0.0.0.0/0") {
+ continue;
+ }
+ if (!is_subnet($natlocal_subnet) && !is_ipaddr($natlocal_subnet)) {
+ continue;
+ }
+ if (!is_array($ph2ent['remoteid'])) {
+ $ph2ent['remoteid'] = array();
+ }
+ $ph2ent['remoteid']['mode'] = $ph2ent['mode'];
+ $remote_subnet = ipsec_idinfo_to_cidr($ph2ent['remoteid']);
+ if (empty($remote_subnet)) {
+ continue;
+ }
+ if (!is_subnet($remote_subnet) && !is_ipaddr($remote_subnet)) {
+ continue;
+ }
+ if ($remote_subnet == "0.0.0.0/0") {
+ $remote_subnet = "any";
+ }
+ if (is_ipaddr($natlocal_subnet) && !is_ipaddr($local_subnet)) {
+ $nattype = "nat";
+ } else {
+ list($natnet, $natmask) = explode('/', $natlocal_subnet);
+ list($locnet, $locmask) = explode('/', $local_subnet);
+ if (intval($natmask) != intval($locmask)) {
+ $nattype = "nat";
+ } else {
+ $nattype = "binat";
+ }
+ unset($natnet, $natmask, $locnet, $locmask);
+ }
+ $natrules .= "{$nattype} on enc0 from {$local_subnet} to {$remote_subnet} -> {$natlocal_subnet}\n";
+ }
+ }
+ }
+ }
+
+ if ($config['nat']['outbound']['mode'] == "disabled") {
+ $natrules .= "\n# Outbound NAT rules are disabled\n";
+ }
+
+ if ($config['nat']['outbound']['mode'] == "advanced" || $config['nat']['outbound']['mode'] == "hybrid") {
+ $natrules .= "\n# Outbound NAT rules (manual)\n";
+ /* advanced outbound rules */
+ if (is_array($config['nat']['outbound']['rule'])) {
+ foreach ($config['nat']['outbound']['rule'] as $obent) {
+ if (isset($obent['disabled'])) {
+ continue;
+ }
+ update_filter_reload_status(sprintf(gettext("Creating advanced outbound rule %s"), $obent['descr']));
+ $src = alias_expand($obent['source']['network']);
+ if (!$src) {
+ $src = $obent['source']['network'];
+ }
+ $dst = alias_expand($obent['destination']['address']);
+ if (!$dst) {
+ $dst = $obent['destination']['address'];
+ }
+ if (isset($obent['destination']['not']) && !isset($obent['destination']['any'])) {
+ $dst = "!" . $dst;
+ }
+
+ if (!$obent['interface'] || !isset($FilterIflist[$obent['interface']])) {
+ continue;
+ }
+
+ $obtarget = ($obent['target'] == "other-subnet") ? $obent['targetip'] . '/' . $obent['targetip_subnet']: $obent['target'];
+ $poolopts = (is_subnet($obtarget) || is_alias($obtarget)) ? $obent['poolopts'] : "";
+
+ $natrules .= filter_nat_rules_generate_if($obent['interface'],
+ $src,
+ $obent['sourceport'],
+ $dst,
+ $obent['dstport'],
+ $obtarget,
+ $obent['natport'],
+ isset($obent['nonat']),
+ isset($obent['staticnatport']),
+ $obent['protocol'],
+ $poolopts
+ );
+ }
+ }
+ }
+
+ /* outbound rules */
+ if ((!isset($config['nat']['outbound']['mode'])) ||
+ ($config['nat']['outbound']['mode'] == "automatic") ||
+ ($config['nat']['outbound']['mode'] == "hybrid")) {
+ $natrules .= "\n# Outbound NAT rules (automatic)\n";
+ /* standard outbound rules (one for each interface) */
+ update_filter_reload_status(gettext("Creating outbound NAT rules"));
+ $tonathosts_array = filter_nat_rules_automatic_tonathosts();
+ $tonathosts = implode(" ", $tonathosts_array);
+ $numberofnathosts = count($tonathosts_array);
+
+ $natrules .= "\n# Subnets to NAT \n";
+ if ($numberofnathosts > 0) {
+ update_filter_reload_status(gettext('Creating automatic outbound rules'));
+
+ if ($numberofnathosts > 4) {
+ $natrules .= "table <tonatsubnets> { {$tonathosts} }\n";
+ $macroortable = "<tonatsubnets>";
+ } else {
+ $natrules .= "tonatsubnets = \"{ {$tonathosts} }\"\n";
+ $macroortable = "\$tonatsubnets";
+ }
+
+ $a_outs = filter_nat_rules_outbound_automatic($macroortable);
+ foreach ($a_outs as $a_out) {
+ $natrules .= filter_nat_rules_generate_if($a_out['interface'],
+ $a_out['source']['network'],
+ $a_out['sourceport'],
+ $a_out['destination']['address'],
+ $a_out['dstport'],
+ $a_out['target'],
+ $a_out['natport'],
+ isset($a_out['nonat']),
+ isset($a_out['staticnatport']));
+ }
+ }
+ unset($tonathosts, $tonathosts_array, $numberofnathosts);
+ }
+
+ /* load balancer anchor */
+ $natrules .= "\n# Load balancing anchor\n";
+ $natrules .= "rdr-anchor \"relayd/*\"\n";
+
+ update_filter_reload_status(gettext("Setting up TFTP helper"));
+ $natrules .= "# TFTP proxy\n";
+ $natrules .= "rdr-anchor \"tftp-proxy/*\"\n";
+
+ if (!empty($config['system']['tftpinterface'])) {
+ $tftpifs = explode(",", $config['system']['tftpinterface']);
+ foreach ($tftpifs as $tftpif) {
+ if ($FilterIflist[$tftpif]) {
+ $natrules .= "rdr pass on {$FilterIflist[$tftpif]['if']} proto udp from any to any port tftp -> 127.0.0.1 port 6969\n";
+ }
+ }
+ }
+
+ /* DIAG: add ipv6 NAT, if requested */
+ if ((isset($config['diag']['ipv6nat']['enable'])) &&
+ (is_ipaddr($config['diag']['ipv6nat']['ipaddr'])) &&
+ (is_array($FilterIflist['wan']))) {
+ /* XXX: FIX ME! IPV6 */
+ $natrules .= "rdr on \${$FilterIflist['wan']['descr']} proto ipv6 from any to any -> {$config['diag']['ipv6nat']['ipaddr']}\n";
+ }
+
+ if (file_exists("/var/etc/inetd.conf")) {
+ @unlink("/var/etc/inetd.conf");
+ }
+ // Open inetd.conf write handle
+ $inetd_fd = fopen("/var/etc/inetd.conf", "w");
+ /* add tftp protocol helper */
+ fwrite($inetd_fd, "tftp-proxy\tdgram\tudp\twait\t\troot\t/usr/libexec/tftp-proxy\ttftp-proxy -v\n");
+
+ if (isset($config['nat']['rule'])) {
+ /* start reflection redirects on port 19000 of localhost */
+ $starting_localhost_port = 19000;
+ $natrules .= "# NAT Inbound Redirects\n";
+ foreach ($config['nat']['rule'] as $rule) {
+ update_filter_reload_status(sprintf(gettext("Creating NAT rule %s"), $rule['descr']));
+
+ if (isset($rule['disabled'])) {
+ continue;
+ }
+
+ /* if item is an alias, expand */
+ $dstport = "";
+ $dstport[0] = alias_expand($rule['destination']['port']);
+ if (!$dstport[0]) {
+ $dstport = explode("-", $rule['destination']['port']);
+ }
+
+ /* if item is an alias, expand */
+ $localport = alias_expand($rule['local-port']);
+ if (!$localport || $dstport[0] == $localport) {
+ $localport = "";
+ } else if (is_alias($rule['local-port'])) {
+ $localport = filter_expand_alias($rule['local-port']);
+ if ($localport) {
+ $localport = explode(" ", trim($localport));
+ $localport = $localport[0];
+ $localport = " port {$localport}";
+ }
+ } else if (is_alias($rule['destination']['port'])) {
+ $localport = " port {$localport}";
+ } else {
+ if (($dstport[1]) && ($dstport[0] != $dstport[1])) {
+ $localendport = $localport + ($dstport[1] - $dstport[0]);
+
+ $localport .= ":$localendport";
+ }
+
+ $localport = " port {$localport}";
+ }
+
+ switch (strtolower($rule['protocol'])) {
+ case "tcp/udp":
+ $protocol = "{ tcp udp }";
+ break;
+ case "tcp":
+ case "udp":
+ $protocol = strtolower($rule['protocol']);
+ break;
+ default:
+ $protocol = strtolower($rule['protocol']);
+ $localport = "";
+ break;
+ }
+
+ $target = alias_expand($rule['target']);
+ if (!$target && !isset($rule['nordr'])) {
+ $natrules .= "# Unresolvable alias {$rule['target']}\n";
+ continue; /* unresolvable alias */
+ }
+
+ if (is_alias($rule['target'])) {
+ $target_ip = filter_expand_alias($rule['target']);
+ } else if (is_ipaddr($rule['target'])) {
+ $target_ip = $rule['target'];
+ } else if (is_ipaddr($FilterIflist[$rule['target']]['ip'])) {
+ $target_ip = $FilterIflist[$rule['target']]['ip'];
+ } else {
+ $target_ip = $rule['target'];
+ }
+ $target_ip = trim($target_ip);
+
+ if ($rule['associated-rule-id'] == "pass") {
+ $rdrpass = "pass ";
+ } else {
+ $rdrpass = "";
+ }
+
+ if (isset($rule['nordr'])) {
+ $nordr = "no ";
+ $rdrpass = "";
+ } else {
+ $nordr = "";
+ }
+
+ if (!$rule['interface']) {
+ $natif = "wan";
+ } else {
+ $natif = $rule['interface'];
+ }
+
+ if (!isset($FilterIflist[$natif])) {
+ continue;
+ }
+
+ $srcaddr = filter_generate_address($rule, 'source', true);
+ $dstaddr = filter_generate_address($rule, 'destination', true);
+ $srcaddr = trim($srcaddr);
+ $dstaddr = trim($dstaddr);
+
+ if (!$dstaddr) {
+ $dstaddr = $FilterIflist[$natif]['ip'];
+ }
+
+ $dstaddr_port = explode(" ", $dstaddr);
+ if (empty($dstaddr_port[0]) || strtolower(trim($dstaddr_port[0])) == "port") {
+ continue; // Skip port forward if no destination address found
+ }
+ $dstaddr_reflect = $dstaddr;
+ if (isset($rule['destination']['any'])) {
+ /* With reflection enabled, destination of 'any' has side effects
+ * that most people would not expect, so change it on reflection rules. */
+
+ if (!empty($FilterIflist[$natif]['ip'])) {
+ $dstaddr_reflect = $FilterIflist[$natif]['ip'];
+ } else {
+ // no IP, bail
+ continue;
+ }
+
+ if (!empty($FilterIflist[$natif]['sn'])) {
+ $dstaddr_reflect = gen_subnet($dstaddr_reflect, $FilterIflist[$natif]['sn']) . '/' . $FilterIflist[$natif]['sn'];
+ }
+
+ if ($dstaddr_port[2]) {
+ $dstaddr_reflect .= " port " . $dstaddr_port[2];
+ }
+ }
+
+ $natif = $FilterIflist[$natif]['if'];
+
+ $reflection_type = "none";
+ if ($rule['natreflection'] != "disable" && $dstaddr_port[0] != "0.0.0.0") {
+ if ($rule['natreflection'] == "enable") {
+ $reflection_type = "proxy";
+ } else if ($rule['natreflection'] == "purenat") {
+ $reflection_type = "purenat";
+ } else if (!isset($config['system']['disablenatreflection'])) {
+ if (isset($config['system']['enablenatreflectionpurenat'])) {
+ $reflection_type = "purenat";
+ } else {
+ $reflection_type = "proxy";
+ }
+ }
+ }
+
+ if ($reflection_type != "none") {
+ $nat_if_list = filter_get_reflection_interfaces($natif);
+ } else {
+ $nat_if_list = array();
+ }
+
+ if (empty($nat_if_list)) {
+ $reflection_type = "none";
+ }
+
+ $localport_nat = $localport;
+ if (empty($localport_nat) && $dstaddr_port[2]) {
+ $localport_nat = " port " . $dstaddr_port[2];
+ }
+
+ if ($srcaddr <> "" && $dstaddr <> "" && $natif) {
+ $natrules .= "{$nordr}rdr {$rdrpass}on {$natif} proto {$protocol} from {$srcaddr} to {$dstaddr}" . ($nordr == "" ? " -> {$target}{$localport}" : "");
+
+ /* Does this rule redirect back to a internal host? */
+ if (isset($rule['destination']['any']) && !isset($rule['nordr']) && !isset($config['system']['enablenatreflectionhelper']) && !interface_has_gateway($rule['interface'])) {
+ $rule_interface_ip = find_interface_ip($natif);
+ $rule_interface_subnet = find_interface_subnet($natif);
+ if (!empty($rule_interface_ip) && !empty($rule_interface_subnet)) {
+ $rule_subnet = gen_subnet($rule_interface_ip, $rule_interface_subnet);
+ $natrules .= "\n";
+ $natrules .= "no nat on {$natif} proto tcp from ({$natif}) to {$rule_subnet}/{$rule_interface_subnet}\n";
+ $natrules .= "nat on {$natif} proto tcp from {$rule_subnet}/{$rule_interface_subnet} to {$target} port {$dstport[0]} -> ({$natif})\n";
+ }
+ }
+
+ if ($reflection_type != "none") {
+ if ($reflection_type == "proxy" && !isset($rule['nordr'])) {
+ $natrules .= filter_generate_reflection_proxy($rule, $nordr, $nat_if_list, $srcaddr, $dstaddr, $starting_localhost_port, $reflection_rules);
+ $nat_if_list = array($natif);
+ foreach ($reflection_rules as $txtline) {
+ fwrite($inetd_fd, $txtline);
+ }
+ } else if ($reflection_type == "purenat" || isset($rule['nordr'])) {
+ $rdr_if_list = implode(" ", $nat_if_list);
+ if (count($nat_if_list) > 1) {
+ $rdr_if_list = "{ {$rdr_if_list} }";
+ }
+ $natrules .= "\n# Reflection redirect\n";
+ $natrules .= "{$nordr}rdr {$rdrpass}on {$rdr_if_list} proto {$protocol} from {$srcaddr} to {$dstaddr_reflect}" . ($nordr == "" ? " -> {$target}{$localport}" : "");
+ $nat_if_list = array_merge(array($natif), $nat_if_list);
+ }
+ }
+
+ if (empty($nat_if_list)) {
+ $nat_if_list = array($natif);
+ }
+
+ $natrules .= "\n";
+ if (!isset($rule['nordr'])) {
+ $natrules .= filter_generate_reflection_nat($rule, $route_table, $nat_if_list, $protocol, "{$target}{$localport_nat}", $target_ip);
+ }
+ }
+ }
+ }
+ fclose($inetd_fd); // Close file handle
+
+ if (isset($config['pptpd']['mode']) && ($config['pptpd']['mode'] != "off")) {
+ if ($config['pptpd']['mode'] == "redir") {
+ $pptpdtarget = $config['pptpd']['redir'];
+ $natrules .= "# PPTP\n";
+ $natrules .= "rdr on \${$FilterIflist['wan']['descr']} proto gre from any to any -> {$pptpdtarget}\n";
+ $natrules .= "rdr on \${$FilterIflist['wan']['descr']} proto tcp from any to any port 1723 -> {$pptpdtarget}\n";
+ }
+ }
+
+ $natrules .= discover_pkg_rules("nat");
+
+ $natrules .= "# UPnPd rdr anchor\n";
+ $natrules .= "rdr-anchor \"miniupnpd\"\n";
+
+ if (!empty($reflection_txt)) {
+ $natrules .= "\n# Reflection redirects and NAT for 1:1 mappings\n" . $reflection_txt;
+ }
+
+ // Check if inetd is running, if not start it. If so, restart it gracefully.
+ $helpers = isvalidproc("inetd");
+ if (file_exists("/var/etc/inetd.conf")) {
+ if (!$helpers) {
+ mwexec("/usr/sbin/inetd -wW -R 0 -a 127.0.0.1 /var/etc/inetd.conf");
+ } else {
+ sigkillbypid("/var/run/inetd.pid", "HUP");
+ }
+ }
+
+ return $natrules;
+}
+
+function filter_generate_user_rule_arr($rule) {
+ global $config;
+ update_filter_reload_status(sprintf(gettext("Creating filter rule %s ..."), $rule['descr']));
+ $ret = array();
+ $line = filter_generate_user_rule($rule);
+ $ret['rule'] = $line;
+ $ret['interface'] = $rule['interface'];
+ if ($rule['descr'] != "" and $line != "") {
+ $ret['descr'] = "label \"" . fix_rule_label("USER_RULE: {$rule['descr']}") . "\"";
+ } else {
+ $ret['descr'] = "label \"USER_RULE\"";
+ }
+
+ return $ret;
+}
+
+function filter_generate_port(& $rule, $target = "source", $isnat = false) {
+
+ $src = "";
+
+ $rule['protocol'] = strtolower($rule['protocol']);
+ if (in_array($rule['protocol'], array("tcp", "udp", "tcp/udp"))) {
+ if ($rule[$target]['port']) {
+ $srcport = explode("-", $rule[$target]['port']);
+ $srcporta = alias_expand($srcport[0]);
+ if (!$srcporta) {
+ log_error(sprintf(gettext("filter_generate_port: %s is not a valid {$target} port."), $srcport[0]));
+ } else if ((!$srcport[1]) || ($srcport[0] == $srcport[1])) {
+ $src .= " port {$srcporta} ";
+ } else if (($srcport[0] == 1) && ($srcport[1] == 65535)) {
+ /* no need for a port statement here */
+ } else if ($isnat) {
+ $src .= " port {$srcport[0]}:{$srcport[1]}";
+ } else {
+ if (is_port($srcporta) && $srcport[1] == 65535) {
+ $src .= " port >= {$srcporta} ";
+ } else if ($srcport[0] == 1) {
+ $src .= " port <= {$srcport[1]} ";
+ } else {
+ $srcport[0]--;
+ $srcport[1]++;
+ $src .= " port {$srcport[0]} >< {$srcport[1]} ";
+ }
+ }
+ }
+ }
+
+ return $src;
+}
+
+function filter_address_add_vips_subnets(&$subnets, $if, $not) {
+ global $FilterIflist;
+
+ $if_subnets = array($subnets);
+
+ if ($not == true) {
+ $subnets = "!{$subnets}";
+ }
+
+ if (!isset($FilterIflist[$if]['vips']) || !is_array($FilterIflist[$if]['vips'])) {
+ return;
+ }
+
+ foreach ($FilterIflist[$if]['vips'] as $vip) {
+ foreach ($if_subnets as $subnet) {
+ if (ip_in_subnet($vip['ip'], $subnet)) {
+ continue 2;
+ }
+ }
+
+ if (is_ipaddrv4($vip['ip'])) {
+ if (!is_subnetv4($if_subnets[0])) {
+ continue;
+ }
+
+ $network = gen_subnet($vip['ip'], $vip['sn']);
+ } else if (is_ipaddrv6($vip['ip'])) {
+ if (!is_subnetv6($if_subnets[0])) {
+ continue;
+ }
+
+ $network = gen_subnetv6($vip['ip'], $vip['sn']);
+ } else {
+ continue;
+ }
+
+ $subnets .= ' ' . ($not == true ? '!' : '') . $network . '/' . $vip['sn'];
+ $if_subnets[] = $network . '/' . $vip['sn'];
+ }
+ unset($if_subnets);
+
+ if (strpos($subnets, ' ') !== false) {
+ $subnets = "{ {$subnets} }";
+ }
+}
+
+function filter_generate_address(& $rule, $target = "source", $isnat = false) {
+ global $FilterIflist, $config;
+ $src = "";
+
+ if (isset($rule[$target]['any'])) {
+ $src = "any";
+ } else if ($rule[$target]['network']) {
+ if (strstr($rule[$target]['network'], "opt")) {
+ $optmatch = "";
+ $matches = "";
+ if ($rule['ipprotocol'] == "inet6") {
+ if (preg_match("/opt([0-9]*)$/", $rule[$target]['network'], $optmatch)) {
+ $opt_sa = $FilterIflist["opt{$optmatch[1]}"]['sav6'];
+ if (!is_ipaddrv6($opt_sa)) {
+ return "";
+ }
+ $src = $opt_sa . "/" . $FilterIflist["opt{$optmatch[1]}"]['snv6'];
+ /* check for opt$NUMip here */
+ } else if (preg_match("/opt([0-9]*)ip/", $rule[$target]['network'], $matches)) {
+ $src = $FilterIflist["opt{$matches[1]}"]['ipv6'];
+ if (!is_ipaddrv6($src)) {
+ return "";
+ }
+ if (isset($rule[$target]['not'])) {
+ $src = " !{$src}";
+ }
+ }
+ } else {
+ if (preg_match("/opt([0-9]*)$/", $rule[$target]['network'], $optmatch)) {
+ $opt_sa = $FilterIflist["opt{$optmatch[1]}"]['sa'];
+ if (!is_ipaddrv4($opt_sa)) {
+ return "";
+ }
+ $src = $opt_sa . "/" . $FilterIflist["opt{$optmatch[1]}"]['sn'];
+ /* check for opt$NUMip here */
+ } else if (preg_match("/opt([0-9]*)ip/", $rule[$target]['network'], $matches)) {
+ $src = $FilterIflist["opt{$matches[1]}"]['ip'];
+ if (!is_ipaddrv4($src)) {
+ return "";
+ }
+ if (isset($rule[$target]['not'])) {
+ $src = " !{$src}";
+ }
+ }
+ }
+ } else {
+ if ($rule['ipprotocol'] == "inet6") {
+ switch ($rule[$target]['network']) {
+ case 'wan':
+ $wansa = $FilterIflist['wan']['sav6'];
+ if (!is_ipaddrv6($wansa)) {
+ return "";
+ }
+ $wansn = $FilterIflist['wan']['snv6'];
+ $src = "{$wansa}/{$wansn}";
+ break;
+ case 'wanip':
+ $src = $FilterIflist["wan"]['ipv6'];
+ if (!is_ipaddrv6($src)) {
+ return "";
+ }
+ break;
+ case 'lanip':
+ $src = $FilterIflist["lan"]['ipv6'];
+ if (!is_ipaddrv6($src)) {
+ return "";
+ }
+ break;
+ case 'lan':
+ $lansa = $FilterIflist['lan']['sav6'];
+ if (!is_ipaddrv6($lansa)) {
+ return "";
+ }
+ $lansn = $FilterIflist['lan']['snv6'];
+ $src = "{$lansa}/{$lansn}";
+ break;
+ case '(self)':
+ $src = "(self)";
+ break;
+ case 'pptp':
+ $pptpsav6 = gen_subnetv6($FilterIflist['pptp']['sav6'], $FilterIflist['pptp']['snv6']);
+ $pptpsnv6 = $FilterIflist['pptp']['snv6'];
+ $src = "{$pptpsav6}/{$pptpsnv6}";
+ break;
+ case 'pppoe':
+ if (is_array($FilterIflist['pppoe'])) {
+ $pppoesav6 = gen_subnetv6($FilterIflist['pppoe'][0]['ipv6'], $FilterIflist['pppoe'][0]['snv6']);
+ $pppoesnv6 = $FilterIflist['pppoe'][0]['snv6'];
+ $src = "{$pppoesav6}/{$pppoesnv6}";
+ }
+ }
+ if (isset($rule[$target]['not']) && !is_subnet($src)) {
+ $src = " !{$src}";
+ }
+ } else {
+ switch ($rule[$target]['network']) {
+ case 'wan':
+ $wansa = $FilterIflist['wan']['sa'];
+ if (!is_ipaddrv4($wansa)) {
+ return "";
+ }
+ $wansn = $FilterIflist['wan']['sn'];
+ $src = "{$wansa}/{$wansn}";
+ break;
+ case 'wanip':
+ $src = $FilterIflist["wan"]['ip'];
+ break;
+ case 'lanip':
+ $src = $FilterIflist["lan"]['ip'];
+ break;
+ case 'lan':
+ $lansa = $FilterIflist['lan']['sa'];
+ if (!is_ipaddrv4($lansa)) {
+ return "";
+ }
+ $lansn = $FilterIflist['lan']['sn'];
+ $src = "{$lansa}/{$lansn}";
+ break;
+ case '(self)':
+ $src = "(self)";
+ break;
+ case 'pptp':
+ if (isset($config['pptpd']['n_pptp_units']) && is_numeric($config['pptpd']['n_pptp_units'])) {
+ $pptp_subnets = ip_range_to_subnet_array($config['pptpd']['remoteip'], long2ip32(ip2long($config['pptpd']['remoteip'])+($config['pptpd']['n_pptp_units']-1)));
+ } else {
+ $pptp_subnets = ip_range_to_subnet_array($config['pptpd']['remoteip'], long2ip32(ip2long($config['pptpd']['remoteip'])));
+ }
+ if (empty($pptp_subnets)) {
+ return "";
+ }
+ if (isset($rule[$target]['not'])) {
+ array_walk($pptp_subnets, function (&$value, $key) {
+ $value="!{$value}";
+ });
+ }
+ $src = "{ " . implode(" ", $pptp_subnets) . " }";
+ break;
+ case 'pppoe':
+ /* XXX: This needs to be fixed somehow! */
+ if (is_array($FilterIflist['pppoe'])) {
+ $pppoesa = gen_subnet($FilterIflist['pppoe'][0]['ip'], $FilterIflist['pppoe'][0]['sn']);
+ $pppoesn = $FilterIflist['pppoe'][0]['sn'];
+ $src = "{$pppoesa}/{$pppoesn}";
+ }
+ break;
+ }
+ if ((isset($rule[$target]['not'])) &&
+ (!is_subnet($src)) &&
+ (strpos($src, '{') === false)) {
+ $src = " !{$src}";
+ }
+ }
+ }
+ if (is_subnet($src)) {
+ filter_address_add_vips_subnets($src, $rule[$target]['network'], isset($rule[$target]['not']));
+ }
+ } else if ($rule[$target]['address']) {
+ $expsrc = alias_expand($rule[$target]['address']);
+ if (isset($rule[$target]['not'])) {
+ $not = "!";
+ } else {
+ $not = "";
+ }
+ $src = " {$not} {$expsrc}";
+ }
+
+ if (empty($src)) {
+ return '';
+ }
+
+ $src .= filter_generate_port($rule, $target, $isnat);
+
+ return $src;
+}
+
+function filter_generate_user_rule($rule) {
+ global $config, $g, $FilterIflist, $GatewaysList;
+ global $layer7_rules_list, $dummynet_name_list;
+
+ if (isset($config['system']['developerspew'])) {
+ $mt = microtime();
+ echo "filter_generate_user_rule() being called $mt\n";
+ }
+ /* don't include disabled rules */
+ if (isset($rule['disabled'])) {
+ return "# rule " . $rule['descr'] . " disabled \n";
+ }
+ update_filter_reload_status("Creating filter rules {$rule['descr']} ...");
+ $pptpdcfg = $config['pptpd'];
+ $int = "";
+ $aline = array();
+
+ /* Check to see if the interface is in our list */
+ if (isset($rule['floating'])) {
+ if (isset($rule['interface']) && $rule['interface'] <> "") {
+ $interfaces = explode(",", $rule['interface']);
+ $ifliste = "";
+ foreach ($interfaces as $iface) {
+ if (array_key_exists($iface, $FilterIflist)) {
+ $ifliste .= " " . $FilterIflist[$iface]['if'] . " ";
+ }
+ }
+ if ($ifliste <> "") {
+ $aline['interface'] = " on { {$ifliste} } ";
+ } else {
+ $aline['interface'] = "";
+ }
+ } else {
+ $aline['interface'] = "";
+ }
+ } else if (!array_key_exists($rule['interface'], $FilterIflist)) {
+ foreach ($FilterIflist as $oc) {
+ $items .= $oc['descr'] . " ";
+ }
+ return "# array key \"{$rule['interface']}\" does not exist for \"" . $rule['descr'] . "\" in array: {{$items}}";
+ } else if ((array_key_exists($rule['interface'], $FilterIflist)) &&
+ (is_array($FilterIflist[$rule['interface']])) &&
+ (is_array($FilterIflist[$rule['interface']][0]))) {
+ /* Currently the only case for this is the pppoe server. There should be an existing macro with this name. */
+ $aline['interface'] = " on \$" . $rule['interface'] . " ";
+ } else {
+ $aline['interface'] = " on \$" . $FilterIflist[$rule['interface']]['descr'] . " ";
+ }
+ $ifcfg = $FilterIflist[$rule['interface']];
+ if ($pptpdcfg['mode'] != "server") {
+ if (($rule['source']['network'] == "pptp") ||
+ ($rule['destination']['network'] == "pptp")) {
+ return "# source network or destination network == pptp on " . $rule['descr'];
+ }
+ }
+
+ switch ($rule['ipprotocol']) {
+ case "inet":
+ $aline['ipprotocol'] = "inet";
+ break;
+ case "inet6":
+ $aline['ipprotocol'] = "inet6";
+ break;
+ default:
+ $aline['ipprotocol'] = "";
+ break;
+ }
+
+ /* check for unresolvable aliases */
+ if ($rule['source']['address'] && !alias_expand($rule['source']['address'])) {
+ $error_text = "Unresolvable source alias '{$rule['source']['address']}' for rule '{$rule['descr']}'";
+ file_notice("Filter_Reload", $error_text);
+ return "# {$error_text}";
+ }
+ if ($rule['destination']['address'] && !alias_expand($rule['destination']['address'])) {
+ $error_text = "Unresolvable destination alias '{$rule['destination']['address']}' for rule '{$rule['descr']}'";
+ file_notice("Filter_Reload", $error_text);
+ return "# {$error_text}";
+ }
+ update_filter_reload_status("Setting up pass/block rules");
+ $type = $rule['type'];
+ if ($type != "pass" && $type != "block" && $type != "reject" && $type != "match") {
+ /* default (for older rules) is pass */
+ $type = "pass";
+ }
+ if ($type == "reject") {
+ $aline['type'] = "block return ";
+ } else {
+ $aline['type'] = $type . " ";
+ }
+ if (isset($rule['floating']) && $rule['floating'] == "yes") {
+ if ($rule['direction'] != "any") {
+ $aline['direction'] = " " . $rule['direction'] . " ";
+ }
+ } else {
+ /* ensure the direction is in */
+ $aline['direction'] = " in ";
+ }
+ if (isset($rule['log'])) {
+ $aline['log'] = "log ";
+ }
+ if (!isset($rule['floating']) || isset($rule['quick'])) {
+ $aline['quick'] = " quick ";
+ }
+
+ /* set the gateway interface */
+ update_filter_reload_status(sprintf(gettext("Setting up pass/block rules %s"), $rule['descr']));
+
+ /* do not process reply-to for gateway'd rules */
+ if ($rule['gateway'] == "" && $aline['direction'] <> "" && (interface_has_gateway($rule['interface']) || interface_has_gatewayv6($rule['interface'])) && !isset($config['system']['disablereplyto']) && !isset($rule['disablereplyto']) && $type != "match") {
+ if ($rule['ipprotocol'] == "inet6") {
+ $rg = get_interface_gateway_v6($rule['interface']);
+ if (is_ipaddrv6($rg)) {
+ $aline['reply'] = "reply-to ( {$ifcfg['ifv6']} {$rg} ) ";
+ } else if ($rule['interface'] <> "pptp") {
+ log_error(sprintf(gettext("Could not find IPv6 gateway for interface (%s)."), $rule['interface']));
+ }
+ } else {
+ $rg = get_interface_gateway($rule['interface']);
+ if (is_ipaddrv4($rg)) {
+ $aline['reply'] = "reply-to ( {$ifcfg['if']} {$rg} ) ";
+ } else if ($rule['interface'] <> "pptp") {
+ log_error(sprintf(gettext("Could not find IPv4 gateway for interface (%s)."), $rule['interface']));
+ }
+ }
+ }
+ /* if user has selected a custom gateway, lets work with it */
+ else if ($rule['gateway'] <> "" && $type == "pass") {
+ if (isset($GatewaysList[$rule['gateway']])) {
+ /* Add the load balanced gateways */
+ $aline['route'] = " \$GW{$rule['gateway']} ";
+ } else if (isset($config['system']['skip_rules_gw_down'])) {
+ return "# rule " . $rule['descr'] . " disabled because gateway " . $rule['gateway'] . " is down ";
+ } else {
+ log_error("The gateway: {$rule['gateway']} is invalid or unknown, not using it.");
+ }
+ }
+
+ if (isset($rule['protocol']) && !empty($rule['protocol'])) {
+ if ($rule['protocol'] == "tcp/udp") {
+ $aline['prot'] = " proto { tcp udp } ";
+ } elseif (($rule['protocol'] == "icmp") && ($rule['ipprotocol'] == "inet6")) {
+ $aline['prot'] = " proto ipv6-icmp ";
+ } elseif ($rule['protocol'] == "icmp") {
+ $aline['prot'] = " proto icmp ";
+ } else {
+ $aline['prot'] = " proto {$rule['protocol']} ";
+ }
+ } else {
+ if ($rule['source']['port'] <> "" || $rule['destination']['port'] <> "") {
+ $aline['prot'] = " proto tcp ";
+ }
+ }
+ update_filter_reload_status(sprintf(gettext("Creating rule %s"), $rule['descr']));
+
+ /* source address */
+ $src = trim(filter_generate_address($rule, "source"));
+ if (empty($src) || ($src == "/")) {
+ return "# at the break!";
+ }
+ $aline['src'] = " from $src ";
+
+ /* OS signatures */
+ if (($rule['protocol'] == "tcp") && ($rule['os'] <> "")) {
+ $aline['os'] = " os \"{$rule['os']}\" ";
+ }
+
+ /* destination address */
+ $dst = trim(filter_generate_address($rule, "destination"));
+ if (empty($dst) || ($dst == "/")) {
+ return "# returning at dst $dst == \"/\"";
+ }
+ $aline['dst'] = "to $dst ";
+
+ //Layer7 support
+ $l7_present = false;
+ $l7_structures = array();
+ if (isset($rule['l7container']) && $rule['l7container'] != "none") {
+ $l7_present = true;
+ $l7rule =& $layer7_rules_list[$rule['l7container']];
+ $l7_structures = $l7rule->get_unique_structures();
+ $aline['divert'] = "divert-to " . $l7rule->GetRPort() . " ";
+ }
+ if (($rule['protocol'] == "icmp") && $rule['icmptype'] && ($rule['ipprotocol'] == "inet")) {
+ $aline['icmp-type'] = "icmp-type {$rule['icmptype']} ";
+ }
+ if (($rule['protocol'] == "icmp") && $rule['icmptype'] && ($rule['ipprotocol'] == "inet6")) {
+ $aline['icmp6-type'] = "icmp6-type {$rule['icmptype']} ";
+ }
+ if (!empty($rule['tag'])) {
+ if (ctype_digit($rule['tag'])) {
+ $aline['tag'] = " tag \"" .$rule['tag']. "\" ";
+ } else {
+ $aline['tag'] = " tag " .$rule['tag']. " ";
+ }
+ }
+ if (!empty($rule['tagged'])) {
+ $aline['tagged'] = " tagged " .$rule['tagged'] . " ";
+ }
+ if (!empty($rule['dscp'])) {
+ switch (strtolower($rule['dscp'])) {
+ case 'va':
+ $aline['dscp'] = " dscp \"44\" ";
+ break;
+ case 'VA':
+ $aline['dscp'] = " dscp \"44\" ";
+ break;
+ case 'cs1':
+ $aline['dscp'] = " dscp \"8\" ";
+ break;
+ case 'cs2':
+ $aline['dscp'] = " dscp \"16\" ";
+ break;
+ case 'cs3':
+ $aline['dscp'] = " dscp \"24\" ";
+ break;
+ case 'cs4':
+ $aline['dscp'] = " dscp \"32\" ";
+ break;
+ case 'cs5':
+ $aline['dscp'] = " dscp \"40\" ";
+ break;
+ case 'cs6':
+ $aline['dscp'] = " dscp \"48\" ";
+ break;
+ case 'cs7':
+ $aline['dscp'] = " dscp \"56\" ";
+ break;
+ default:
+ $aline['dscp'] = " dscp " . $rule['dscp'] . " ";
+ break;
+ }
+ }
+ if (!empty($rule['vlanprio']) && ($rule['vlanprio'] != "none")) {
+ $aline['vlanprio'] = " ieee8021q-pcp " . $rule['vlanprio'] . " ";
+ }
+ if (!empty($rule['vlanprioset']) && ($rule['vlanprioset'] != "none")) {
+ $aline['vlanprioset'] = " ieee8021q-setpcp " . $rule['vlanprioset'] . " ";
+ }
+ if ($type == "pass") {
+ if (isset($rule['allowopts'])) {
+ $aline['allowopts'] = " allow-opts ";
+ }
+ }
+ $aline['flags'] = "";
+ if ($rule['protocol'] == "tcp") {
+ if (isset($rule['tcpflags_any'])) {
+ $aline['flags'] = "flags any ";
+ } else if (!empty($rule['tcpflags2'])) {
+ $aline['flags'] = "flags ";
+ if (!empty($rule['tcpflags1'])) {
+ $flags1 = explode(",", $rule['tcpflags1']);
+ foreach ($flags1 as $flag1) {
+ // CWR flag needs special treatment
+ if ($flag1[0] == "c") {
+ $aline['flags'] .= "W";
+ } else {
+ $aline['flags'] .= strtoupper($flag1[0]);
+ }
+ }
+ }
+ $aline['flags'] .= "/";
+ if (!empty($rule['tcpflags2'])) {
+ $flags2 = explode(",", $rule['tcpflags2']);
+ foreach ($flags2 as $flag2) {
+ // CWR flag needs special treatment
+ if ($flag2[0] == "c") {
+ $aline['flags'] .= "W";
+ } else {
+ $aline['flags'] .= strtoupper($flag2[0]);
+ }
+ }
+ }
+ $aline['flags'] .= " ";
+ } else {
+ $aline['flags'] = "flags S/SA ";
+ }
+ }
+ if ($type == "pass") {
+ /*
+ * # keep state
+ * works with TCP, UDP, and ICMP.
+ * # modulate state
+ * works only with TCP. pfSense will generate strong Initial Sequence Numbers (ISNs)
+ * for packets matching this rule.
+ * # synproxy state
+ * proxies incoming TCP connections to help protect servers from spoofed TCP SYN floods.
+ * This option includes the functionality of keep state and modulate state combined.
+ * # none
+ * do not use state mechanisms to keep track. this is only useful if your doing advanced
+ * queueing in certain situations. please check the faq.
+ */
+ $noadvoptions = false;
+ if (isset($rule['statetype']) && $rule['statetype'] <> "") {
+ switch ($rule['statetype']) {
+ case "none":
+ $noadvoptions = true;
+ $aline['flags'] .= " no state ";
+ break;
+ case "modulate state":
+ case "synproxy state":
+ if ($rule['protocol'] == "tcp") {
+ $aline['flags'] .= "{$rule['statetype']} ";
+ }
+ break;
+ case "sloppy state":
+ $aline['flags'] .= "keep state ";
+ $rule['sloppy'] = true;
+ break;
+ default:
+ $aline['flags'] .= "{$rule['statetype']} ";
+ break;
+ }
+ } else {
+ $aline['flags'] .= "keep state ";
+ }
+
+ if ($noadvoptions == false && isset($rule['nopfsync'])) {
+ $rule['nopfsync'] = true;
+ }
+
+ if ($noadvoptions == false || $l7_present) {
+ if ((isset($rule['source-track']) and $rule['source-track'] <> "") or
+ (isset($rule['max']) and $rule['max'] <> "") or
+ (isset($rule['max-src-nodes']) and $rule['max-src-nodes'] <> "") or
+ (isset($rule['max-src-states']) and $rule['max-src-states'] <> "") or
+ ((in_array($rule['protocol'], array("tcp", "tcp/udp"))) and
+ ((isset($rule['statetimeout']) and $rule['statetimeout'] <> "") or
+ (isset($rule['max-src-conn']) and $rule['max-src-conn'] <> "") or
+ (isset($rule['max-src-conn-rate']) and $rule['max-src-conn-rate'] <> "") or
+ (isset($rule['max-src-conn-rates']) and $rule['max-src-conn-rates'] <> ""))) or
+ (isset($rule['sloppy'])) or
+ (isset($rule['nopfsync'])) or
+ ($l7_present)) {
+ $aline['flags'] .= "( ";
+ if (isset($rule['sloppy'])) {
+ $aline['flags'] .= "sloppy ";
+ }
+ if (isset($rule['nopfsync'])) {
+ $aline['flags'] .= "no-sync ";
+ }
+ if (isset($rule['source-track']) and $rule['source-track'] <> "") {
+ $aline['flags'] .= "source-track rule ";
+ }
+ if (isset($rule['max']) and $rule['max'] <> "") {
+ $aline['flags'] .= "max " . $rule['max'] . " ";
+ }
+ if (isset($rule['max-src-nodes']) and $rule['max-src-nodes'] <> "") {
+ $aline['flags'] .= "max-src-nodes " . $rule['max-src-nodes'] . " ";
+ }
+ if ((in_array($rule['protocol'], array("tcp", "tcp/udp"))) and
+ (isset($rule['max-src-conn'])) and
+ ($rule['max-src-conn'] <> "")) {
+ $aline['flags'] .= "max-src-conn " . $rule['max-src-conn'] . " ";
+ }
+ if (isset($rule['max-src-states']) and $rule['max-src-states'] <> "") {
+ $aline['flags'] .= "max-src-states " . $rule['max-src-states'] . " ";
+ }
+ if ((in_array($rule['protocol'], array("tcp", "tcp/udp"))) and
+ (isset($rule['statetimeout'])) and
+ ($rule['statetimeout'] <> "")) {
+ $aline['flags'] .= "tcp.established " . $rule['statetimeout'] . " ";
+ }
+ if ((in_array($rule['protocol'], array("tcp", "tcp/udp"))) and
+ (isset($rule['max-src-conn-rate'])) and
+ ($rule['max-src-conn-rate'] <> "") and
+ (isset($rule['max-src-conn-rates'])) and
+ ($rule['max-src-conn-rates'] <> "")) {
+ $aline['flags'] .= "max-src-conn-rate " . $rule['max-src-conn-rate'] . " ";
+ $aline['flags'] .= "/" . $rule['max-src-conn-rates'] . ", overload <virusprot> flush global ";
+ }
+
+ if (!empty($aline['divert'])) {
+ $aline['flags'] .= "max-packets 8 ";
+ }
+
+ $aline['flags'] .= " ) ";
+ }
+ }
+ }
+ if ($rule['defaultqueue'] <> "") {
+ $aline['queue'] = " queue (".$rule['defaultqueue'];
+ if ($rule['ackqueue'] <> "") {
+ $aline['queue'] .= "," . $rule['ackqueue'];
+ }
+ $aline['queue'] .= ") ";
+ }
+ if ($rule['dnpipe'] <> "") {
+ if (!empty($dummynet_name_list[$rule['dnpipe']])) {
+ if ($dummynet_name_list[$rule['dnpipe']][0] == "?") {
+ $aline['dnpipe'] = " dnqueue( ";
+ $aline['dnpipe'] .= substr($dummynet_name_list[$rule['dnpipe']], 1);
+ if ($rule['pdnpipe'] <> "") {
+ $aline['dnpipe'] .= "," . substr($dummynet_name_list[$rule['pdnpipe']], 1);
+ }
+ } else {
+ $aline['dnpipe'] = " dnpipe ( " . $dummynet_name_list[$rule['dnpipe']];
+ if ($rule['pdnpipe'] <> "") {
+ $aline['dnpipe'] .= "," . $dummynet_name_list[$rule['pdnpipe']];
+ }
+ }
+ $aline['dnpipe'] .= ") ";
+ }
+ }
+
+ /* is a time based rule schedule attached? */
+ if (!empty($rule['sched']) && !empty($config['schedules'])) {
+ $aline['schedlabel'] = "";
+ foreach ($config['schedules']['schedule'] as $sched) {
+ if ($sched['name'] == $rule['sched']) {
+ if (!filter_get_time_based_rule_status($sched)) {
+ if (!isset($config['system']['schedule_states'])) {
+ mwexec("/sbin/pfctl -y {$sched['schedlabel']}");
+ }
+ return "# schedule finished - {$rule['descr']}";
+ } else if ($g['debug']) {
+ log_error("[TDR DEBUG] status true -- rule type '$type'");
+ }
+
+ $aline['schedlabel'] = " schedule \"{$sched['schedlabel']}\" ";
+ break;
+ }
+ }
+ }
+
+ if (!empty($rule['tracker'])) {
+ $aline['tracker'] = "tracker {$rule['tracker']} ";
+ }
+
+ $line = "";
+ /* exception(s) to a user rules can go here. */
+ /* rules with a gateway or pool should create another rule for routing to vpns */
+ if ((($aline['route'] <> "") && (trim($aline['type']) == "pass") && strstr($dst, "any")) && (!isset($config['system']['disablenegate']))) {
+ /* negate VPN/PPTP/PPPoE/Static Route networks for load balancer/gateway rules */
+ $negate_networks = " to <negate_networks> " . filter_generate_port($rule, "destination");
+ $line .= $aline['type'] . $aline['direction'] . $aline['log'] . $aline['quick'] .
+ $aline['interface'] . $aline['ipprotocol'] . $aline['prot'] . $aline['src'] . $aline['os'] .
+ $negate_networks . $aline['icmp-type'] . $aline['icmp6-type'] . $aline['tag'] . $aline['tagged'] .
+ $aline['vlanprio'] . $aline['vlanprioset'] . $aline['dscp'] . filter_negaterule_tracker() . $aline['allowopts'] . $aline['flags'] .
+ $aline['queue'] . $aline['dnpipe'] . $aline['schedlabel'] .
+ " label \"NEGATE_ROUTE: Negate policy routing for destination\"\n";
+
+ }
+ /* piece together the actual user rule */
+ $line .= $aline['type'] . $aline['direction'] . $aline['log'] . $aline['quick'] . $aline['interface'] .
+ $aline['reply'] . $aline['route'] . $aline['ipprotocol'] . $aline['prot'] . $aline['src'] . $aline['os'] . $aline['dst'] .
+ $aline['divert'] . $aline['icmp-type'] . $aline['icmp6-type'] . $aline['tag'] . $aline['tagged'] . $aline['dscp'] . $aline['tracker'] .
+ $aline['vlanprio'] . $aline['vlanprioset'] . $aline['allowopts'] . $aline['flags'] . $aline['queue'] . $aline['dnpipe'] . $aline['schedlabel'];
+
+ unset($aline);
+
+ return $line;
+}
+
+function filter_rules_generate() {
+ global $config, $g, $FilterIflist, $time_based_rules, $GatewaysList, $tracker;
+
+ $fix_rule_label = 'fix_rule_label';
+ $increment_tracker = 'filter_rule_tracker';
+
+ update_filter_reload_status(gettext("Creating default rules"));
+ if (isset($config['system']['developerspew'])) {
+ $mt = microtime();
+ echo "filter_rules_generate() being called $mt\n";
+ }
+
+ $pptpdcfg = $config['pptpd'];
+
+ $ipfrules = "";
+ $ipfrules .= discover_pkg_rules("pfearly");
+
+ /* relayd */
+ $ipfrules .= "anchor \"relayd/*\"\n";
+ /* OpenVPN user rules from radius */
+ $ipfrules .= "anchor \"openvpn/*\"\n";
+ /* IPsec user rules from radius */
+ $ipfrules .= "anchor \"ipsec/*\"\n";
+ # BEGIN OF firewall rules
+ /* default block logging? */
+ $log = array();
+ if (!isset($config['syslog']['nologdefaultblock'])) {
+ $log['block'] = "log";
+ }
+ if (isset($config['syslog']['nologdefaultpass'])) {
+ $log['pass'] = "log";
+ }
+
+ $saved_tracker = $tracker;
+
+ if (!isset($config['system']['ipv6allow'])) {
+ $ipfrules .= "# Allow IPv6 on loopback\n";
+ $ipfrules .= "pass in {$log['pass']} quick on \$loopback inet6 all tracker {$increment_tracker($tracker)} label \"pass IPv6 loopback\"\n";
+ $ipfrules .= "pass out {$log['pass']} quick on \$loopback inet6 all tracker {$increment_tracker($tracker)} label \"pass IPv6 loopback\"\n";
+ $ipfrules .= "# Block all IPv6\n";
+ $ipfrules .= "block in {$log['block']} quick inet6 all tracker {$increment_tracker($tracker)} label \"Block all IPv6\"\n";
+ $ipfrules .= "block out {$log['block']} quick inet6 all tracker {$increment_tracker($tracker)} label \"Block all IPv6\"\n";
+ }
+
+ $saved_tracker += 100;
+ $tracker = $saved_tracker;
+
+ if (!isset($config['system']['no_apipa_block'])) {
+ $ipfrules .= <<<EOD
+# block IPv4 link-local. Per RFC 3927, link local "MUST NOT" be forwarded by a routing device,
+# and clients "MUST NOT" send such packets to a router. FreeBSD won't route 169.254./16, but
+# route-to can override that, causing problems such as in redmine #2073
+block in {$log['block']} quick from 169.254.0.0/16 to any tracker {$increment_tracker($tracker)} label "Block IPv4 link-local"
+block in {$log['block']} quick from any to 169.254.0.0/16 tracker {$increment_tracker($tracker)} label "Block IPv4 link-local"
+
+EOD;
+ }
+
+ $ipfrules .= <<<EOD
+#---------------------------------------------------------------------------
+# default deny rules
+#---------------------------------------------------------------------------
+block in {$log['block']} inet all tracker {$increment_tracker($tracker)} label "Default deny rule IPv4"
+block out {$log['block']} inet all tracker {$increment_tracker($tracker)} label "Default deny rule IPv4"
+block in {$log['block']} inet6 all tracker {$increment_tracker($tracker)} label "Default deny rule IPv6"
+block out {$log['block']} inet6 all tracker {$increment_tracker($tracker)} label "Default deny rule IPv6"
+
+# IPv6 ICMP is not auxilary, it is required for operation
+# See man icmp6(4)
+# 1 unreach Destination unreachable
+# 2 toobig Packet too big
+# 128 echoreq Echo service request
+# 129 echorep Echo service reply
+# 133 routersol Router solicitation
+# 134 routeradv Router advertisement
+# 135 neighbrsol Neighbor solicitation
+# 136 neighbradv Neighbor advertisement
+pass {$log['pass']} quick inet6 proto ipv6-icmp from any to any icmp6-type {1,2,135,136} tracker {$increment_tracker($tracker)} keep state
+
+# Allow only bare essential icmpv6 packets (NS, NA, and RA, echoreq, echorep)
+pass out {$log['pass']} quick inet6 proto ipv6-icmp from fe80::/10 to fe80::/10 icmp6-type {129,133,134,135,136} tracker {$increment_tracker($tracker)} keep state
+pass out {$log['pass']} quick inet6 proto ipv6-icmp from fe80::/10 to ff02::/16 icmp6-type {129,133,134,135,136} tracker {$increment_tracker($tracker)} keep state
+pass in {$log['pass']} quick inet6 proto ipv6-icmp from fe80::/10 to fe80::/10 icmp6-type {128,133,134,135,136} tracker {$increment_tracker($tracker)} keep state
+pass in {$log['pass']} quick inet6 proto ipv6-icmp from ff02::/16 to fe80::/10 icmp6-type {128,133,134,135,136} tracker {$increment_tracker($tracker)} keep state
+pass in {$log['pass']} quick inet6 proto ipv6-icmp from fe80::/10 to ff02::/16 icmp6-type {128,133,134,135,136} tracker {$increment_tracker($tracker)} keep state
+
+# We use the mighty pf, we cannot be fooled.
+block {$log['block']} quick inet proto { tcp, udp } from any port = 0 to any tracker {$increment_tracker($tracker)} label "Block traffic from port 0"
+block {$log['block']} quick inet proto { tcp, udp } from any to any port = 0 tracker {$increment_tracker($tracker)} label "Block traffic to port 0"
+block {$log['block']} quick inet6 proto { tcp, udp } from any port = 0 to any tracker {$increment_tracker($tracker)} label "Block traffic from port 0"
+block {$log['block']} quick inet6 proto { tcp, udp } from any to any port = 0 tracker {$increment_tracker($tracker)} label "Block traffic to port 0"
+
+# Snort package
+block {$log['block']} quick from <snort2c> to any tracker {$increment_tracker($tracker)} label "Block snort2c hosts"
+block {$log['block']} quick from any to <snort2c> tracker {$increment_tracker($tracker)} label "Block snort2c hosts"
+
+EOD;
+
+ $saved_tracker += 100;
+ $tracker = $saved_tracker;
+
+ $ipfrules .= filter_process_carp_rules($log);
+
+ $saved_tracker += 100;
+ $tracker = $saved_tracker;
+
+ $ipfrules .= "\n# SSH lockout\n";
+ if (is_array($config['system']['ssh']) && !empty($config['system']['ssh']['port'])) {
+ $ipfrules .= "block in {$log['block']} quick proto tcp from <sshlockout> to (self) port ";
+ $ipfrules .= $config['system']['ssh']['port'];
+ $ipfrules .= " tracker {$increment_tracker($tracker)} label \"sshlockout\"\n";
+ } else {
+ if ($config['system']['ssh']['port'] <> "") {
+ $sshport = $config['system']['ssh']['port'];
+ } else {
+ $sshport = 22;
+ }
+ if ($sshport) {
+ $ipfrules .= "block in {$log['block']} quick proto tcp from <sshlockout> to (self) port {$sshport} tracker {$increment_tracker($tracker)} label \"sshlockout\"\n";
+ }
+ }
+
+ $saved_tracker += 50;
+ $tracker = $saved_tracker;
+
+ $ipfrules .= "\n# webConfigurator lockout\n";
+ if (!$config['system']['webgui']['port']) {
+ if ($config['system']['webgui']['protocol'] == "http") {
+ $webConfiguratorlockoutport = "80";
+ } else {
+ $webConfiguratorlockoutport = "443";
+ }
+ } else {
+ $webConfiguratorlockoutport = $config['system']['webgui']['port'];
+ }
+ if ($webConfiguratorlockoutport) {
+ $ipfrules .= "block in {$log['block']} quick proto tcp from <webConfiguratorlockout> to (self) port {$webConfiguratorlockoutport} tracker {$increment_tracker($tracker)} label \"webConfiguratorlockout\"\n";
+ }
+
+ $saved_tracker += 100;
+ $tracker = $saved_tracker;
+
+ /*
+ * Support for allow limiting of TCP connections by establishment rate
+ * Useful for protecting against sudden outbursts, etc.
+ */
+ $ipfrules .= "block in {$log['block']} quick from <virusprot> to any tracker 1000000400 label \"virusprot overload table\"\n";
+
+ $saved_tracker += 100;
+ $tracker = $saved_tracker;
+
+ /* if captive portal is enabled, ensure that access to this port
+ * is allowed on a locked down interface
+ */
+ if (is_array($config['captiveportal'])) {
+ foreach ($config['captiveportal'] as $cpcfg) {
+ if (!isset($cpcfg['enable'])) {
+ continue;
+ }
+ $cpinterfaces = explode(",", $cpcfg['interface']);
+ $cpiflist = array();
+ $cpiplist = array();
+ foreach ($cpinterfaces as $cpifgrp) {
+ if (!isset($FilterIflist[$cpifgrp])) {
+ continue;
+ }
+ $tmpif = get_real_interface($cpifgrp);
+ if (!empty($tmpif)) {
+ $cpiflist[] = "{$tmpif}";
+ $cpipm = get_interface_ip($cpifgrp);
+ if (is_ipaddr($cpipm)) {
+ $cpiplist[] = $cpipm;
+ if (!is_array($config['virtualip']) || !is_array($config['virtualip']['vip'])) {
+ continue;
+ }
+ foreach ($config['virtualip']['vip'] as $vip) {
+ if (($vip['interface'] == $cpifgrp) && (($vip['mode'] == "carp") || ($vip['mode'] == "ipalias"))) {
+ $cpiplist[] = $vip['subnet'];
+ }
+ }
+ }
+ }
+ }
+ if (count($cpiplist) > 0 && count($cpiflist) > 0) {
+ $cpinterface = implode(" ", $cpiflist);
+ $cpaddresses = implode(" ", $cpiplist);
+ $listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : 8000 + ($cpcfg['zoneid'] + 1);
+ $listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : 8000 + $cpcfg['zoneid'];
+ $portalias = $listenporthttps;
+ $portalias .= " {$listenporthttp}";
+ $ipfrules .= "pass in {$log['pass']} quick on { {$cpinterface} } proto tcp from any to { {$cpaddresses} } port { {$portalias} } tracker {$increment_tracker($tracker)} keep state(sloppy)\n";
+ $ipfrules .= "pass out {$log['pass']} quick on { {$cpinterface} } proto tcp from any to any flags any tracker {$increment_tracker($tracker)} keep state(sloppy)\n";
+ }
+ }
+ }
+
+ $bogontableinstalled = 0;
+ foreach ($FilterIflist as $on => $oc) {
+ $saved_tracker += 10;
+ $tracker = $saved_tracker;
+
+ if (isset($config['system']['ipv6allow']) && ($oc['type6'] == "slaac" || $oc['type6'] == "dhcp6")) {
+ // The DHCPv6 client rules ***MUST BE ABOVE BOGONSV6!*** https://redmine.pfsense.org/issues/3395
+ $ipfrules .= <<<EOD
+# allow our DHCPv6 client out to the {$oc['descr']}
+pass in {$log['pass']} quick on \${$oc['descr']} proto udp from fe80::/10 port = 546 to fe80::/10 port = 546 tracker {$increment_tracker($tracker)} label "{$fix_rule_label("allow dhcpv6 client in {$oc['descr']}")}"
+pass in {$log['pass']} quick on \${$oc['descr']} proto udp from any port = 547 to any port = 546 tracker {$increment_tracker($tracker)} label "{$fix_rule_label("allow dhcpv6 client in {$oc['descr']}")}"
+pass out {$log['pass']} quick on \${$oc['descr']} proto udp from any port = 546 to any port = 547 tracker {$increment_tracker($tracker)} label "{$fix_rule_label("allow dhcpv6 client out {$oc['descr']}")}"
+
+EOD;
+ }
+
+ /* XXX: Not static but give a step of 1000 for each interface to at least be able to match rules. */
+ $saved_tracker += 1000;
+ $tracker = $saved_tracker;
+
+ /* block bogon networks */
+ /* http://www.cymru.com/Documents/bogon-bn-nonagg.txt */
+ /* file is automatically in cron every 3000 minutes */
+ if (!isset($config['syslog']['nologbogons'])) {
+ $bogonlog = "log";
+ } else {
+ $bogonlog = "";
+ }
+
+ if (isset($config['interfaces'][$on]['blockbogons'])) {
+ $ipfrules .= <<<EOD
+# block bogon networks (IPv4)
+# http://www.cymru.com/Documents/bogon-bn-nonagg.txt
+block in $bogonlog quick on \${$oc['descr']} from <bogons> to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("block bogon IPv4 networks from {$oc['descr']}")}"
+
+EOD;
+
+ if (isset($config['system']['ipv6allow'])) {
+ $ipfrules .= <<<EOD
+# block bogon networks (IPv6)
+# http://www.team-cymru.org/Services/Bogons/fullbogons-ipv6.txt
+block in $bogonlog quick on \${$oc['descr']} from <bogonsv6> to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("block bogon IPv6 networks from {$oc['descr']}")}"
+
+EOD;
+ }
+ }
+
+ $saved_tracker += 10;
+ $tracker = $saved_tracker;
+
+ $isbridged = false;
+ if (is_array($config['bridges']['bridged'])) {
+ foreach ($config['bridges']['bridged'] as $oc2) {
+ if (stristr($oc2['members'], $on)) {
+ $isbridged = true;
+ break;
+ }
+ }
+ }
+
+ if ($oc['ip'] && !($isbridged) && isset($oc['spoofcheck'])) {
+ $ipfrules .= filter_rules_spoofcheck_generate($on, $oc, $log);
+ }
+
+ /* block private networks ? */
+ if (!isset($config['syslog']['nologprivatenets'])) {
+ $privnetlog = "log";
+ } else {
+ $privnetlog = "";
+ }
+
+ $saved_tracker += 10;
+ $tracker = $saved_tracker;
+
+ if (isset($config['interfaces'][$on]['blockpriv'])) {
+ if ($isbridged == false) {
+ $ipfrules .= <<<EOD
+# block anything from private networks on interfaces with the option set
+block in $privnetlog quick on \${$oc['descr']} from 10.0.0.0/8 to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Block private networks from {$oc['descr']} block 10/8")}"
+block in $privnetlog quick on \${$oc['descr']} from 127.0.0.0/8 to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Block private networks from {$oc['descr']} block 127/8")}"
+block in $privnetlog quick on \${$oc['descr']} from 172.16.0.0/12 to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Block private networks from {$oc['descr']} block 172.16/12")}"
+block in $privnetlog quick on \${$oc['descr']} from 192.168.0.0/16 to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Block private networks from {$oc['descr']} block 192.168/16")}"
+block in $privnetlog quick on \${$oc['descr']} from fc00::/7 to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Block ULA networks from {$oc['descr']} block fc00::/7")}"
+
+EOD;
+ }
+ }
+
+ $saved_tracker += 10;
+ $tracker = $saved_tracker;
+
+ switch ($oc['type']) {
+ case "pptp":
+ $ipfrules .= <<<EOD
+# allow PPTP client
+pass in {$log['pass']} on \${$oc['descr']} proto tcp from any to any port = 1723 flags S/SA modulate state tracker {$increment_tracker($tracker)} label "{$fix_rule_label("allow PPTP client on {$oc['descr']}")}"
+pass in {$log['pass']} on \${$oc['descr']} proto gre from any to any keep state tracker {$increment_tracker($tracker)} label "{$fix_rule_label("allow PPTP client on {$oc['descr']}")}"
+
+EOD;
+ break;
+ case "dhcp":
+ $ipfrules .= <<<EOD
+# allow our DHCP client out to the {$oc['descr']}
+pass in {$log['pass']} on \${$oc['descr']} proto udp from any port = 67 to any port = 68 tracker {$increment_tracker($tracker)} label "{$fix_rule_label("allow dhcp client out {$oc['descr']}")}"
+pass out {$log['pass']} on \${$oc['descr']} proto udp from any port = 68 to any port = 67 tracker {$increment_tracker($tracker)} label "{$fix_rule_label("allow dhcp client out {$oc['descr']}")}"
+# Not installing DHCP server firewall rules for {$oc['descr']} which is configured for DHCP.
+
+EOD;
+
+ break;
+ case "pppoe":
+ case "none":
+ /* XXX: Nothing to do in this case?! */
+ break;
+ default:
+ /* allow access to DHCP server on interfaces */
+ if (isset($config['dhcpd'][$on]['enable'])) {
+ $ipfrules .= <<<EOD
+# allow access to DHCP server on {$oc['descr']}
+pass in {$log['pass']} quick on \${$oc['descr']} proto udp from any port = 68 to 255.255.255.255 port = 67 tracker {$increment_tracker($tracker)} label "allow access to DHCP server"
+
+EOD;
+ if (is_ipaddrv4($oc['ip'])) {
+ $ipfrules .= <<<EOD
+pass in {$log['pass']} quick on \${$oc['descr']} proto udp from any port = 68 to {$oc['ip']} port = 67 tracker {$increment_tracker($tracker)} label "allow access to DHCP server"
+pass out {$log['pass']} quick on \${$oc['descr']} proto udp from {$oc['ip']} port = 67 to any port = 68 tracker {$increment_tracker($tracker)} label "allow access to DHCP server"
+
+EOD;
+ }
+
+ if (is_ipaddrv4($oc['ip']) && $config['dhcpd'][$on]['failover_peerip'] <> "") {
+ $ipfrules .= <<<EOD
+# allow access to DHCP failover on {$oc['descr']} from {$config['dhcpd'][$on]['failover_peerip']}
+pass in {$log['pass']} quick on \${$oc['descr']} proto { tcp udp } from {$config['dhcpd'][$on]['failover_peerip']} to {$oc['ip']} port = 519 tracker {$increment_tracker($tracker)} label "allow access to DHCP failover"
+pass in {$log['pass']} quick on \${$oc['descr']} proto { tcp udp } from {$config['dhcpd'][$on]['failover_peerip']} to {$oc['ip']} port = 520 tracker {$increment_tracker($tracker)} label "allow access to DHCP failover"
+
+EOD;
+ }
+
+ }
+ break;
+ }
+
+ $saved_tracker += 10;
+ $tracker = $saved_tracker;
+ switch ($oc['type6']) {
+ case "6rd":
+ $ipfrules .= <<<EOD
+# allow our proto 41 traffic from the 6RD border relay in
+pass in {$log['pass']} on \${$oc['descr']} proto 41 from {$config['interfaces'][$on]['gateway-6rd']} to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Allow 6in4 traffic in for 6rd on {$oc['descr']}")}"
+pass out {$log['pass']} on \${$oc['descr']} proto 41 from any to {$config['interfaces'][$on]['gateway-6rd']} tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Allow 6in4 traffic out for 6rd on {$oc['descr']}")}"
+
+EOD;
+ /* XXX: Really need to allow 6rd traffic coming in for v6 this is against default behaviour! */
+ if (0 && is_ipaddrv6($oc['ipv6'])) {
+ $ipfrules .= <<<EOD
+pass in {$log['pass']} on \${$oc['descr']} inet6 from any to {$oc['ipv6']}/{$oc['snv6']} tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Allow 6rd traffic in for 6rd on {$oc['descr']}")}"
+pass out {$log['pass']} on \${$oc['descr']} inet6 from {$oc['ipv6']}/{$oc['snv6']} to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Allow 6rd traffic out for 6rd on {$oc['descr']}")}"
+
+EOD;
+ }
+ break;
+ case "6to4":
+ if (is_ipaddrv4($oc['ip'])) {
+ $ipfrules .= <<<EOD
+# allow our proto 41 traffic from the 6to4 border relay in
+pass in {$log['pass']} on \${$oc['descr']} proto 41 from any to {$oc['ip']} tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Allow 6in4 traffic in for 6to4 on {$oc['descr']}")}"
+pass out {$log['pass']} on \${$oc['descr']} proto 41 from {$oc['ip']} to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Allow 6in4 traffic out for 6to4 on {$oc['descr']}")}"
+
+EOD;
+ }
+ /* XXX: Really need to allow 6to4 traffic coming in for v6 this is against default behaviour! */
+ if (0 && is_ipaddrv6($oc['ipv6'])) {
+ $ipfrules .= <<<EOD
+pass in {$log['pass']} on \${$oc['descr']} inet6 from any to {$oc['ipv6']}/{$oc['snv6']} tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Allow 6in4 traffic in for 6to4 on {$oc['descr']}")}"
+pass out {$log['pass']} on \${$oc['descr']} inet6 from {$oc['ipv6']}/{$oc['snv6']} to any tracker {$increment_tracker($tracker)} label "{$fix_rule_label("Allow 6in4 traffic out for 6to4 on {$oc['descr']}")}"
+
+EOD;
+ }
+ break;
+ default:
+ if ((is_array($config['dhcpdv6'][$on]) && isset($config['dhcpdv6'][$on]['enable'])) ||
+ (isset($oc['track6-interface'])) ||
+ (is_array($config['dhcrelay6']) && !empty($config['dhcrelay6']['interface']) && in_array($on, explode(',', $config['dhcrelay6']['interface'])))) {
+ $ipfrules .= <<<EOD
+# allow access to DHCPv6 server on {$oc['descr']}
+# We need inet6 icmp for stateless autoconfig and dhcpv6
+pass {$log['pass']} quick on \${$oc['descr']} inet6 proto udp from fe80::/10 to fe80::/10 port = 546 tracker {$increment_tracker($tracker)} label "allow access to DHCPv6 server"
+pass {$log['pass']} quick on \${$oc['descr']} inet6 proto udp from fe80::/10 to ff02::/16 port = 546 tracker {$increment_tracker($tracker)} label "allow access to DHCPv6 server"
+pass {$log['pass']} quick on \${$oc['descr']} inet6 proto udp from fe80::/10 to ff02::/16 port = 547 tracker {$increment_tracker($tracker)} label "allow access to DHCPv6 server"
+pass {$log['pass']} quick on \${$oc['descr']} inet6 proto udp from ff02::/16 to fe80::/10 port = 547 tracker {$increment_tracker($tracker)} label "allow access to DHCPv6 server"
+
+EOD;
+ if (is_ipaddrv6($oc['ipv6'])) {
+ $ipfrules .= <<<EOD
+pass in {$log['pass']} quick on \${$oc['descr']} inet6 proto udp from fe80::/10 to {$oc['ipv6']} port = 546 tracker {$increment_tracker($tracker)} label "allow access to DHCPv6 server"
+pass out {$log['pass']} quick on \${$oc['descr']} inet6 proto udp from {$oc['ipv6']} port = 547 to fe80::/10 tracker {$increment_tracker($tracker)} label "allow access to DHCPv6 server"
+
+EOD;
+ }
+ }
+ break;
+ }
+ }
+
+ $saved_tracker += 10;
+ $tracker = $saved_tracker;
+
+ /*
+ * NB: The loopback rules are needed here since the antispoof would take precedence then.
+ * If you ever add the 'quick' keyword to the antispoof rules above move the loopback
+ * rules before them.
+ */
+ $ipfrules .= <<<EOD
+
+# loopback
+pass in {$log['pass']} on \$loopback inet all tracker {$increment_tracker($tracker)} label "pass IPv4 loopback"
+pass out {$log['pass']} on \$loopback inet all tracker {$increment_tracker($tracker)} label "pass IPv4 loopback"
+pass in {$log['pass']} on \$loopback inet6 all tracker {$increment_tracker($tracker)} label "pass IPv6 loopback"
+pass out {$log['pass']} on \$loopback inet6 all tracker {$increment_tracker($tracker)} label "pass IPv6 loopback"
+# let out anything from the firewall host itself and decrypted IPsec traffic
+pass out {$log['pass']} inet all keep state allow-opts tracker {$increment_tracker($tracker)} label "let out anything IPv4 from firewall host itself"
+pass out {$log['pass']} inet6 all keep state allow-opts tracker {$increment_tracker($tracker)} label "let out anything IPv6 from firewall host itself"
+
+EOD;
+
+ $saved_tracker += 100;
+ $tracker = $saved_tracker;
+ foreach ($FilterIflist as $ifdescr => $ifcfg) {
+ if (isset($ifcfg['virtual'])) {
+ continue;
+ }
+
+ $gw = get_interface_gateway($ifdescr);
+ if (is_ipaddrv4($gw) && is_ipaddrv4($ifcfg['ip'])) {
+ $ipfrules .= "pass out {$log['pass']} route-to ( {$ifcfg['if']} {$gw} ) from {$ifcfg['ip']} to !{$ifcfg['sa']}/{$ifcfg['sn']} tracker {$increment_tracker($tracker)} keep state allow-opts label \"let out anything from firewall host itself\"\n";
+ if (is_array($ifcfg['vips'])) {
+ foreach ($ifcfg['vips'] as $vip) {
+ if (ip_in_subnet($vip['ip'], "{$ifcfg['sa']}/{$ifcfg['sn']}")) {
+ $ipfrules .= "pass out {$log['pass']} route-to ( {$ifcfg['if']} {$gw} ) from {$vip['ip']} to !{$ifcfg['sa']}/{$ifcfg['sn']} tracker {$increment_tracker($tracker)} keep state allow-opts label \"let out anything from firewall host itself\"\n";
+ } else {
+ $ipfrules .= "pass out {$log['pass']} route-to ( {$ifcfg['if']} {$gw} ) from {$vip['ip']} to !" . gen_subnet($vip['ip'], $vip['sn']) . "/{$vip['sn']} tracker {$increment_tracker($tracker)} keep state allow-opts label \"let out anything from firewall host itself\"\n";
+ }
+ }
+ }
+ }
+
+ $gwv6 = get_interface_gateway_v6($ifdescr);
+ $stf = get_real_interface($ifdescr, "inet6");
+ $pdlen = 64 - calculate_ipv6_delegation_length($ifdescr);
+ if (is_ipaddrv6($gwv6) && is_ipaddrv6($ifcfg['ipv6'])) {
+ $ipfrules .= "pass out {$log['pass']} route-to ( {$stf} {$gwv6} ) inet6 from {$ifcfg['ipv6']} to !{$ifcfg['ipv6']}/{$pdlen} tracker {$increment_tracker($tracker)} keep state allow-opts label \"let out anything from firewall host itself\"\n";
+ if (is_array($ifcfg['vips6'])) {
+ foreach ($ifcfg['vips6'] as $vip) {
+ $ipfrules .= "pass out {$log['pass']} route-to ( {$stf} {$gwv6} ) inet6 from {$vip['ip']} to !{$vip['ip']}/{$pdlen} tracker {$increment_tracker($tracker)} keep state allow-opts label \"let out anything from firewall host itself\"\n";
+ }
+ }
+ }
+ }
+
+
+ $saved_tracker += 300;
+ $tracker = $saved_tracker;
+ /* add ipsec interfaces */
+ if (isset($config['ipsec']['enable']) || isset($config['ipsec']['client']['enable'])) {
+ $ipfrules .= "pass out {$log['pass']} on \$IPsec all tracker {$increment_tracker($tracker)} tracker {$increment_tracker($tracker)} keep state label \"IPsec internal host to host\"\n";
+ }
+
+ $saved_tracker += 10;
+ $tracker = $saved_tracker;
+ if (is_array($config['system']['webgui']) && !isset($config['system']['webgui']['noantilockout'])) {
+ $alports = filter_get_antilockout_ports();
+
+ if (count($config['interfaces']) > 1 && !empty($FilterIflist['lan']['if'])) {
+ /* if antilockout is enabled, LAN exists and has
+ * an IP and subnet mask assigned
+ */
+ $lanif = $FilterIflist['lan']['if'];
+ $ipfrules .= <<<EOD
+# make sure the user cannot lock himself out of the webConfigurator or SSH
+pass in {$log['pass']} quick on {$lanif} proto tcp from any to ({$lanif}) port { {$alports} } tracker {$increment_tracker($tracker)} keep state label "anti-lockout rule"
+
+EOD;
+ } else if (count($config['interfaces']) == 1) {
+ /* single-interface deployment, add to WAN */
+ $wanif = $FilterIflist["wan"]['if'];
+ $ipfrules .= <<<EOD
+# make sure the user cannot lock himself out of the webConfigurator or SSH
+pass in {$log['pass']} quick on {$wanif} proto tcp from any to ({$wanif}) port { {$alports} } tracker {$increment_tracker($tracker)} keep state label "anti-lockout rule"
+
+EOD;
+ }
+ unset($alports);
+ }
+
+ $saved_tracker += 10;
+ $tracker = $saved_tracker;
+ /* PPTPd enabled? */
+ if ($pptpdcfg['mode'] && ($pptpdcfg['mode'] != "off") && !isset($config['system']['disablevpnrules'])) {
+ if ($pptpdcfg['mode'] == "server") {
+ $pptpdtarget = get_interface_ip();
+ } else {
+ $pptpdtarget = $pptpdcfg['redir'];
+ }
+ if (is_ipaddr($pptpdtarget) and is_array($FilterIflist['wan'])) {
+ $ipfrules .= <<<EOD
+# PPTPd rules
+pass in {$log['pass']} on \${$FilterIflist['wan']['descr']} proto tcp from any to $pptpdtarget port = 1723 tracker {$increment_tracker($tracker)} modulate state label "{$fix_rule_label("allow pptpd {$pptpdtarget}")}"
+pass in {$log['pass']} on \${$FilterIflist['wan']['descr']} proto gre from any to any tracker {$increment_tracker($tracker)} keep state label "allow gre pptpd"
+
+EOD;
+
+ } else {
+ /* this shouldnt ever happen but instead of breaking the clients ruleset
+ * log an error.
+ */
+ log_error("ERROR! PPTP enabled but could not resolve the \$pptpdtarget");
+ }
+ }
+
+ $saved_tracker += 10;
+ $tracker = $saved_tracker;
+ if (isset($config['nat']['rule']) && is_array($config['nat']['rule'])) {
+ foreach ($config['nat']['rule'] as $rule) {
+ if ((!isset($config['system']['disablenatreflection']) || $rule['natreflection'] == "enable") &&
+ ($rule['natreflection'] != "disable")) {
+ $ipfrules .= "# NAT Reflection rules\n";
+ $ipfrules .= <<<EOD
+pass in {$log['pass']} inet tagged PFREFLECT tracker {$increment_tracker($tracker)} keep state label "NAT REFLECT: Allow traffic to localhost"
+
+EOD;
+ break;
+ }
+ }
+ }
+
+ if (isset($config['filter']['rule'])) {
+ /* Pre-cache all our rules so we only have to generate them once */
+ $rule_arr1 = array();
+ $rule_arr2 = array();
+ $rule_arr3 = array();
+ $vpn_and_ppp_ifs = array("l2tp", "pptp", "pppoe", "enc0", "openvpn");
+ /*
+ * NB: The order must be: Floating rules, then interface group and then regular ones.
+ */
+ foreach ($config['filter']['rule'] as $rule) {
+ update_filter_reload_status("Pre-caching {$rule['descr']}...");
+ if (isset ($rule['disabled'])) {
+ continue;
+ }
+
+ if (!empty($rule['ipprotocol']) && $rule['ipprotocol'] == "inet46") {
+ if (isset($rule['floating'])) {
+ $rule['ipprotocol'] = "inet";
+ $rule_arr1[] = filter_generate_user_rule_arr($rule);
+ $rule['ipprotocol'] = "inet6";
+ $rule_arr1[] = filter_generate_user_rule_arr($rule);
+ } else if (is_interface_group($rule['interface']) || in_array($rule['interface'], $vpn_and_ppp_ifs)) {
+ $rule['ipprotocol'] = "inet";
+ $rule_arr2[] = filter_generate_user_rule_arr($rule);
+ $rule['ipprotocol'] = "inet6";
+ $rule_arr2[] = filter_generate_user_rule_arr($rule);
+ } else {
+ $rule['ipprotocol'] = "inet";
+ $rule_arr3[] = filter_generate_user_rule_arr($rule);
+ $rule['ipprotocol'] = "inet6";
+ $rule_arr3[] = filter_generate_user_rule_arr($rule);
+ }
+ $rule['ipprotocol'] = "inet46";
+ } else {
+ if (isset($rule['floating'])) {
+ $rule_arr1[] = filter_generate_user_rule_arr($rule);
+ } else if (is_interface_group($rule['interface']) || in_array($rule['interface'], $vpn_and_ppp_ifs)) {
+ $rule_arr2[] = filter_generate_user_rule_arr($rule);
+ } else {
+ $rule_arr3[] = filter_generate_user_rule_arr($rule);
+ }
+ }
+ if ($rule['sched']) {
+ $time_based_rules = true;
+ }
+ }
+
+ $ipfrules .= "\n# User-defined rules follow\n";
+ $ipfrules .= "\nanchor \"userrules/*\"\n";
+ /* Generate user rule lines */
+ foreach ($rule_arr1 as $rule) {
+ if (isset($rule['disabled'])) {
+ continue;
+ }
+ if (!$rule['rule']) {
+ continue;
+ }
+ $ipfrules .= "{$rule['rule']} {$rule['descr']}\n";
+ }
+ foreach ($rule_arr2 as $rule) {
+ if (isset($rule['disabled'])) {
+ continue;
+ }
+ if (!$rule['rule']) {
+ continue;
+ }
+ $ipfrules .= "{$rule['rule']} {$rule['descr']}\n";
+ }
+ foreach ($rule_arr3 as $rule) {
+ if (isset($rule['disabled'])) {
+ continue;
+ }
+ if (!$rule['rule']) {
+ continue;
+ }
+ $ipfrules .= "{$rule['rule']} {$rule['descr']}\n";
+ }
+ unset($rule_arr1, $rule_arr2, $rule_arr3);
+ }
+
+ $saved_tracker += 100;
+ $tracker = $saved_tracker;
+
+ /* pass traffic between statically routed subnets and the subnet on the
+ * interface in question to avoid problems with complicated routing
+ * topologies
+ */
+ if (isset($config['filter']['bypassstaticroutes']) && is_array($config['staticroutes']['route']) && count($config['staticroutes']['route'])) {
+ $ipfrules .= "# Add rules to bypass firewall rules for static routes\n";
+ foreach (get_staticroutes() as $route) {
+ $friendly = $GatewaysList[$route['gateway']]['friendlyiface'];
+ if (is_array($FilterIflist[$friendly])) {
+ $oc = $FilterIflist[$friendly];
+ $routeent = explode("/", $route['network']);
+ unset($sa);
+ if (is_ipaddrv4($oc['ip'])) {
+ $sa = $oc['sa'];
+ $sn = $oc['sn'];
+ }
+ if ($sa && is_ipaddrv4($routeent[0])) {
+ $ipfrules .= <<<EOD
+pass {$log['pass']} quick on \${$oc['descr']} proto tcp from {$sa}/{$sn} to {$route['network']} flags any tracker {$increment_tracker($tracker)} keep state(sloppy) label "pass traffic between statically routed subnets"
+pass {$log['pass']} quick on \${$oc['descr']} from {$sa}/{$sn} to {$route['network']} tracker {$increment_tracker($tracker)} keep state(sloppy) label "pass traffic between statically routed subnets"
+pass {$log['pass']} quick on \${$oc['descr']} proto tcp from {$route['network']} to {$sa}/{$sn} flags any tracker {$increment_tracker($tracker)} keep state(sloppy) label "pass traffic between statically routed subnets"
+pass {$log['pass']} quick on \${$oc['descr']} from {$route['network']} to {$sa}/{$sn} tracker {$increment_tracker($tracker)} keep state(sloppy) label "pass traffic between statically routed subnets"
+
+EOD;
+ }
+ unset($sa);
+ if (is_ipaddrv6($oc['ipv6'])) {
+ $sa = $oc['sav6'];
+ $sn = $oc['snv6'];
+ }
+ if ($sa && is_ipaddrv6($routeent[0])) {
+ $ipfrules .= <<<EOD
+pass {$log['pass']} quick on \${$oc['descr']} inet6 proto tcp from {$sa}/{$sn} to {$route['network']} flags any tracker {$increment_tracker($tracker)} keep state(sloppy) label "pass traffic between statically routed subnets"
+pass {$log['pass']} quick on \${$oc['descr']} inet6 from {$sa}/{$sn} to {$route['network']} tracker {$increment_tracker($tracker)} keep state(sloppy) label "pass traffic between statically routed subnets"
+pass {$log['pass']} quick on \${$oc['descr']} inet6 proto tcp from {$route['network']} to {$sa}/{$sn} flags any tracker {$increment_tracker($tracker)} keep state(sloppy) label "pass traffic between statically routed subnets"
+pass {$log['pass']} quick on \${$oc['descr']} inet6 from {$route['network']} to {$sa}/{$sn} tracker {$increment_tracker($tracker)} keep state(sloppy) label "pass traffic between statically routed subnets"
+
+EOD;
+ }
+ }
+ }
+ }
+
+ update_filter_reload_status(gettext("Creating IPsec rules..."));
+ $saved_tracker += 100000;
+ $tracker = $saved_tracker;
+ $ipfrules .= filter_generate_ipsec_rules($log);
+
+ $ipfrules .= "\nanchor \"tftp-proxy/*\"\n";
+
+ $saved_tracker += 200;
+ $tracker = $saved_tracker;
+ update_filter_reload_status("Creating uPNP rules...");
+ if (is_array($config['installedpackages']['miniupnpd']) && is_array($config['installedpackages']['miniupnpd']['config'][0])) {
+ if (isset($config['installedpackages']['miniupnpd']['config'][0]['enable'])) {
+ $ipfrules .= "anchor \"miniupnpd\"\n";
+ }
+
+ if (is_array($config['installedpackages']['miniupnpd'][0]['config'])) {
+ $upnp_interfaces = explode(",", $config['installedpackages']['miniupnpd'][0]['config']['iface_array']);
+ foreach ($upnp_interfaces as $upnp_if) {
+ if (is_array($FilterIflist[$upnp_if])) {
+ $oc = $FilterIflist[$upnp_if];
+ unset($sa);
+ if ($oc['ip']) {
+ $sa = $oc['sa'];
+ $sn = $oc['sn'];
+ }
+ if ($sa) {
+ $ipfrules .= <<<EOD
+pass in {$log['pass']} on \${$oc['descr']} proto tcp from {$sa}/{$sn} to 239.255.255.250/32 port 1900 tracker {$increment_tracker($tracker)} keep state label "pass multicast traffic to miniupnpd"
+
+EOD;
+ }
+ }
+ }
+ }
+ }
+
+ return $ipfrules;
+}
+
+function filter_rules_spoofcheck_generate($ifname, $ifcfg, $log) {
+ global $g, $config, $tracker;
+ if (isset($config['system']['developerspew'])) {
+ $mt = microtime();
+ echo "filter_rules_spoofcheck_generate() being called $mt\n";
+ }
+ $ipfrules = "antispoof {$log['block']} for \${$ifcfg['descr']} tracker {$tracker}\n";
+ $tracker++;
+
+ return $ipfrules;
+}
+
+/* COMPAT Function */
+function tdr_install_cron($should_install) {
+ log_error(gettext("Please use filter_tdr_install_cron() function tdr_install_cron will be deprecated!"));
+ filter_tdr_install_cron($should_install);
+}
+
+/****f* filter/filter_tdr_install_cron
+ * NAME
+ * filter_tdr_install_cron
+ * INPUTS
+ * $should_install true if the cron entry should be installed, false
+ * if the entry should be removed if it is present
+ * RESULT
+ * none
+ ******/
+function filter_tdr_install_cron($should_install) {
+ global $config, $g;
+
+ if (platform_booting() == true) {
+ return;
+ }
+
+ if (!is_array($config['cron'])) {
+ $config['cron'] = array();
+ }
+ if (!is_array($config['cron']['item'])) {
+ $config['cron']['item'] = array();
+ }
+
+ $x = 0;
+ $is_installed = false;
+ foreach ($config['cron']['item'] as $item) {
+ if (strstr($item['command'], "filter_configure_sync")) {
+ $is_installed = true;
+ break;
+ }
+ $x++;
+ }
+
+ switch ($should_install) {
+ case true:
+ if (!$is_installed) {
+ $cron_item = array();
+ $cron_item['minute'] = "0,15,30,45";
+ $cron_item['hour'] = "*";
+ $cron_item['mday'] = "*";
+ $cron_item['month'] = "*";
+ $cron_item['wday'] = "*";
+ $cron_item['who'] = "root";
+ $cron_item['command'] = "/etc/rc.filter_configure_sync";
+ $config['cron']['item'][] = $cron_item;
+ write_config(gettext("Installed 15 minute filter reload for Time Based Rules"));
+ configure_cron();
+ }
+ break;
+ case false:
+ if ($is_installed) {
+ unset($config['cron']['item'][$x]);
+ write_config(gettext("Removed 15 minute filter reload for Time Based Rules"));
+ configure_cron();
+ }
+ break;
+ }
+}
+
+/****f* filter/filter_get_time_based_rule_status
+ * NAME
+ * filter_get_time_based_rule_status
+ * INPUTS
+ * xml schedule block
+ * RESULT
+ * true/false - true if the rule should be installed
+ ******/
+/*
+ <schedules>
+ <schedule>
+ <name>ScheduleMultipleTime</name>
+ <descr>main descr</descr>
+ <time>
+ <position>0,1,2</position>
+ <hour>0:0-24:0</hour>
+ <desc>time range 2</desc>
+ </time>
+ <time>
+ <position>4,5,6</position>
+ <hour>0:0-24:0</hour>
+ <desc>time range 1</desc>
+ </time>
+ </schedule>
+ </schedules>
+*/
+function filter_get_time_based_rule_status($schedule) {
+
+ /* no schedule? rule should be installed */
+ if (empty($schedule)) {
+ return true;
+ }
+ /*
+ * iterate through time blocks and determine
+ * if the rule should be installed or not.
+ */
+ foreach ($schedule['timerange'] as $timeday) {
+ if (empty($timeday['month'])) {
+ $monthstatus = true;
+ } else {
+ $monthstatus = filter_tdr_month($timeday['month']);
+ }
+ if (empty($timeday['day'])) {
+ $daystatus = true;
+ } else {
+ $daystatus = filter_tdr_day($timeday['day']);
+ }
+ if (empty($timeday['hour'])) {
+ $hourstatus = true;
+ } else {
+ $hourstatus = filter_tdr_hour($timeday['hour']);
+ }
+ if (empty($timeday['position'])) {
+ $positionstatus = true;
+ } else {
+ $positionstatus = filter_tdr_position($timeday['position']);
+ }
+
+ if ($monthstatus == true && $daystatus == true && $positionstatus == true && $hourstatus == true) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function filter_tdr_day($schedule) {
+ global $g;
+
+ if ($g['debug']) {
+ log_error("[TDR DEBUG] filter_tdr_day($schedule)");
+ }
+
+ /*
+ * Calculate day of month.
+ * IE: 29th of may
+ */
+ $date = date("d");
+ $defined_days = explode(",", $schedule);
+ foreach ($defined_days as $dd) {
+ if ($date == $dd) {
+ return true;
+ }
+ }
+ return false;
+}
+function filter_tdr_hour($schedule) {
+ global $g;
+
+ /* $schedule should be a string such as 16:00-19:00 */
+ $tmp = explode("-", $schedule);
+ $starting_time = strtotime($tmp[0]);
+ $ending_time = strtotime($tmp[1]);
+ $now = strtotime("now");
+ if ($g['debug']) {
+ log_error("[TDR DEBUG] S: $starting_time E: $ending_time N: $now");
+ }
+ if ($now >= $starting_time and $now < $ending_time) {
+ return true;
+ }
+ return false;
+}
+
+function filter_tdr_position($schedule) {
+ global $g;
+
+ /*
+ * Calculate position, ie: day of week.
+ * Sunday = 7, Monday = 1, Tuesday = 2
+ * Weds = 3, Thursday = 4, Friday = 5,
+ * Saturday = 6
+ * ...
+ */
+ $weekday = date("w");
+ if ($g['debug']) {
+ log_error("[TDR DEBUG] filter_tdr_position($schedule) $weekday");
+ }
+ if ($weekday == 0) {
+ $weekday = 7;
+ }
+ $schedule_days = explode(",", $schedule);
+ foreach ($schedule_days as $day) {
+ if ($day == $weekday) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function filter_tdr_month($schedule) {
+ global $g;
+
+ /*
+ * Calculate month
+ */
+ $todays_month = date("n");
+ $months = explode(",", $schedule);
+ if ($g['debug']) {
+ log_error("[TDR DEBUG] filter_tdr_month($schedule)");
+ }
+ foreach ($months as $month) {
+ if ($month == $todays_month) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function filter_setup_logging_interfaces() {
+ global $config, $FilterIflist;
+
+ if (isset($config['system']['developerspew'])) {
+ $mt = microtime();
+ echo "filter_setup_logging_interfaces() being called $mt\n";
+ }
+ $rules = "";
+ if (isset($FilterIflist['lan'])) {
+ $rules .= "set loginterface {$FilterIflist['lan']['if']}\n";
+ } else if (isset($FilterIflist['wan'])) {
+ $rules .= "set loginterface {$FilterIflist['wan']['if']}\n";
+ }
+
+ return $rules;
+}
+
+function filter_process_carp_rules($log) {
+ global $g, $config, $tracker;
+
+ if (isset($config['system']['developerspew'])) {
+ $mt = microtime();
+ echo "filter_process_carp_rules() being called $mt\n";
+ }
+
+ $increment_tracker = 'filter_rule_tracker';
+ $lines = "";
+ /* return if there are no carp configured items */
+ if (!empty($config['hasync']) or !empty($config['virtualip']['vip'])) {
+ $lines .= "block in {$log['block']} quick proto carp from (self) to any tracker {$increment_tracker($tracker)}\n";
+ $lines .= "pass {$log['pass']} quick proto carp tracker {$increment_tracker($tracker)}\n";
+ }
+ return $lines;
+}
+
+/* Generate IPsec Filter Items */
+function filter_generate_ipsec_rules($log = array()) {
+ global $config, $g, $FilterIflist, $tracker;
+
+ if (isset($config['system']['developerspew'])) {
+ $mt = microtime();
+ echo "filter_generate_ipsec_rules() being called $mt\n";
+ }
+
+ if (isset($config['system']['disablevpnrules'])) {
+ return "\n# VPN Rules not added disabled in System->Advanced.\n";
+ }
+
+ $increment_tracker = 'filter_rule_tracker';
+
+ $ipfrules = "\n# VPN Rules\n";
+ if ((isset($config['ipsec']['enable'])) &&
+ (is_array($config['ipsec']['phase1']))) {
+ /* step through all phase1 entries */
+ foreach ($config['ipsec']['phase1'] as $ph1ent) {
+ $tracker += 10;
+
+ if (isset ($ph1ent['disabled'])) {
+ continue;
+ }
+ /* determine local and remote peer addresses */
+ if (!isset($ph1ent['mobile'])) {
+ if (!function_exists('ipsec_get_phase1_dst')) {
+ require_once("ipsec.inc");
+ }
+ $rgip = ipsec_get_phase1_dst($ph1ent);
+ if (!$rgip) {
+ $ipfrules .= "# ERROR! Unable to determine remote IPsec peer address for {$ph1ent['remote-gateway']}\n";
+ continue;
+ }
+ } else {
+ $rgip = " any ";
+ }
+ /* Determine best description */
+ if ($ph1ent['descr']) {
+ $descr = $ph1ent['descr'];
+ } else {
+ $descr = $rgip;
+ }
+ /*
+ * Step through all phase2 entries and determine
+ * which protocols are in use with this peer
+ */
+ $prot_used_esp = false;
+ $prot_used_ah = false;
+ if (is_array($config['ipsec']['phase2'])) {
+ foreach ($config['ipsec']['phase2'] as $ph2ent) {
+ /* only evaluate ph2's bound to our ph1 */
+ if ($ph2ent['ikeid'] != $ph1ent['ikeid']) {
+ continue;
+ }
+ if ($ph2ent['protocol'] == 'esp') {
+ $prot_used_esp = true;
+ }
+ if ($ph2ent['protocol'] == 'ah') {
+ $prot_used_ah = true;
+ }
+ }
+ }
+
+ if (strpos($ph1ent['interface'], "_vip")) {
+ $parentinterface = get_configured_carp_interface_list($ph1ent['interface'], '', 'iface');
+ } else {
+ $parentinterface = $ph1ent['interface'];
+ }
+ if (empty($FilterIflist[$parentinterface]['descr'])) {
+ $ipfrules .= "# Could not locate interface for IPsec: {$descr}\n";
+ continue;
+ }
+
+ unset($gateway);
+ /* add endpoint routes to correct gateway on interface if the
+ remote endpoint is not on this interface's subnet */
+ if ((isset($ph1ent['mobile']) || is_ipaddrv4($rgip)) && (interface_has_gateway($parentinterface))) {
+ $parentifsubnet = get_interface_ip($parentinterface) . "/" . get_interface_subnet($parentinterface);
+ if (isset($ph1ent['mobile']) || !ip_in_subnet($rgip, $parentifsubnet)) {
+ $gateway = get_interface_gateway($parentinterface);
+ $interface = $FilterIflist[$parentinterface]['if'];
+
+ $route_to = " route-to ( $interface $gateway ) ";
+ $reply_to = " reply-to ( $interface $gateway ) ";
+ }
+ } else if ((isset($ph1ent['mobile']) || is_ipaddrv6($rgip)) && (interface_has_gatewayv6($parentinterface))) {
+ $parentifsubnet = get_interface_ipv6($parentinterface) . "/" . get_interface_subnetv6($parentinterface);
+ if (isset($ph1ent['mobile']) || !ip_in_subnet($rgip, $parentifsubnet)) {
+ $gateway = get_interface_gateway_v6($parentinterface);
+ $interface = $FilterIflist[$parentinterface]['if'];
+
+ $route_to = " route-to ( $interface $gateway ) ";
+ $reply_to = " reply-to ( $interface $gateway ) ";
+ }
+ }
+
+ /* Just in case */
+ if ((!is_ipaddr($gateway) || empty($interface))) {
+ $route_to = " ";
+ $reply_to = " ";
+ }
+
+ /* Add rules to allow IKE to pass */
+ $shorttunneldescr = substr($descr, 0, 35);
+ $ipfrules .= <<<EOD
+pass out {$log['pass']} $route_to proto udp from any to {$rgip} port = 500 tracker {$increment_tracker($tracker)} keep state label "IPsec: {$shorttunneldescr} - outbound isakmp"
+pass in {$log['pass']} on \${$FilterIflist[$parentinterface]['descr']} $reply_to proto udp from {$rgip} to any port = 500 tracker {$increment_tracker($tracker)} keep state label "IPsec: {$shorttunneldescr} - inbound isakmp"
+
+EOD;
+ /* If NAT-T is enabled, add additional rules */
+ if ($ph1ent['nat_traversal'] != "off") {
+ $ipfrules .= <<<EOD
+pass out {$log['pass']} $route_to proto udp from any to {$rgip} port = 4500 tracker {$increment_tracker($tracker)} keep state label "IPsec: {$shorttunneldescr} - outbound nat-t"
+pass in {$log['pass']} on \${$FilterIflist[$parentinterface]['descr']} $reply_to proto udp from {$rgip} to any port = 4500 tracker {$increment_tracker($tracker)} keep state label "IPsec: {$shorttunneldescr} - inbound nat-t"
+
+EOD;
+ }
+ /* Add rules to allow the protocols in use */
+ if ($prot_used_esp) {
+ $ipfrules .= <<<EOD
+pass out {$log['pass']} $route_to proto esp from any to {$rgip} tracker {$increment_tracker($tracker)} keep state label "IPsec: {$shorttunneldescr} - outbound esp proto"
+pass in {$log['pass']} on \${$FilterIflist[$parentinterface]['descr']} $reply_to proto esp from {$rgip} to any tracker {$increment_tracker($tracker)} keep state label "IPsec: {$shorttunneldescr} - inbound esp proto"
+
+EOD;
+ }
+ if ($prot_used_ah) {
+ $ipfrules .= <<<EOD
+pass out {$log['pass']} $route_to proto ah from any to {$rgip} tracker {$increment_tracker($tracker)} keep state label "IPsec: {$shorttunneldescr} - outbound ah proto"
+pass in {$log['pass']} on \${$FilterIflist[$parentinterface]['descr']} $reply_to proto ah from {$rgip} to any tracker {$increment_tracker($tracker)} keep state label "IPsec: {$shorttunneldescr} - inbound ah proto"
+
+EOD;
+ }
+ }
+ }
+ return($ipfrules);
+}
+
+function discover_pkg_rules($ruletype) {
+ global $config, $g, $aliases;
+
+ /* Bail if there is no pkg directory, or if the package files might be out of sync. */
+ if (!is_dir("/usr/local/pkg") || file_exists('/conf/needs_package_sync')) {
+ return "";
+ }
+
+ $rules = "";
+ $files = glob("/usr/local/pkg/*.inc");
+ foreach ($files as $pkg_inc) {
+ update_filter_reload_status(sprintf(gettext('Checking for %1$s PF hooks in package %2$s'), $ruletype, $pkg_inc));
+ $pkg = basename($pkg_inc, ".inc");
+ $pkg_generate_rules = "{$pkg}_generate_rules";
+ if (!function_exists($pkg_generate_rules)) {
+ require_once($pkg_inc);
+ }
+ if (function_exists($pkg_generate_rules)) {
+ update_filter_reload_status(sprintf(gettext('Processing early %1$s rules for package %2$s'), $ruletype, $pkg_inc));
+ $tmprules = $pkg_generate_rules("$ruletype");
+ file_put_contents("{$g['tmp_path']}/rules.test.packages", $aliases . $tmprules);
+ $status = mwexec("/sbin/pfctl -nf {$g['tmp_path']}/rules.test.packages");
+ if ($status <> 0) {
+ $errorrules = sprintf(gettext("There was an error while parsing the package filter rules for %s."), $pkg_inc) . "\n";
+ log_error($errorrules);
+ file_put_contents("{$g['tmp_path']}/rules.packages.{$pkg}", "#{$errorrules}\n{$tmprules}\n");
+ continue;
+ }
+ $rules .= $tmprules;
+ }
+ }
+ return $rules;
+}
+
+function filter_get_antilockout_ports($wantarray = false) {
+ global $config;
+
+ $lockoutports = array();
+ $guiport = ($config['system']['webgui']['protocol'] == "https") ? "443" : "80";
+ $guiport = empty($config['system']['webgui']['port']) ? $guiport : $config['system']['webgui']['port'];
+ $lockoutports[] = $guiport;
+
+ if (($config['system']['webgui']['protocol'] == "https") && !isset($config['system']['webgui']['disablehttpredirect']) && ($guiport != "80")) {
+ $lockoutports[] = "80";
+ }
+
+ if (isset($config['system']['enablesshd'])) {
+ $lockoutports[] = empty($config['system']['ssh']['port']) ? "22" : $config['system']['ssh']['port'];
+ }
+
+ if ($wantarray) {
+ return $lockoutports;
+ } else {
+ return implode(" ", $lockoutports);
+ }
+
+}
+
+?>
diff --git a/src/etc/inc/filter_log.inc b/src/etc/inc/filter_log.inc
new file mode 100644
index 0000000..999d81a
--- /dev/null
+++ b/src/etc/inc/filter_log.inc
@@ -0,0 +1,441 @@
+<?php
+/* $Id$ */
+/*
+ filter_log.inc
+ part of pfSesne by Scott Ullrich
+ originally based on m0n0wall (http://m0n0.ch/wall)
+
+ Copyright (C) 2009 Jim Pingle <myfirstname>@<mylastname>.org
+ 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.
+*/
+/*
+ pfSense_BUILDER_BINARIES: /usr/sbin/fifolog_reader /usr/bin/tail /usr/local/sbin/clog
+ pfSense_MODULE: filter
+*/
+
+require 'config.inc';
+
+global $buffer_rules_rdr, $buffer_rules_normal;
+$buffer_rules_rdr = array();
+$buffer_rules_normal = array();
+
+/* format filter logs */
+function conv_log_filter($logfile, $nentries, $tail = 50, $filtertext = "", $filterinterface = null) {
+ global $config, $g;
+
+ /* Make sure this is a number before using it in a system call */
+ if (!(is_numeric($tail))) {
+ return;
+ }
+
+ if ($filtertext) {
+ $tail = 5000;
+ }
+
+ /* Always do a reverse tail, to be sure we're grabbing the 'end' of the log. */
+ $logarr = "";
+
+ if (isset($config['system']['usefifolog'])) {
+ exec("/usr/sbin/fifolog_reader " . escapeshellarg($logfile) . " | /usr/bin/grep 'filterlog:' | /usr/bin/tail -r -n {$tail}", $logarr);
+ } else {
+ exec("/usr/local/sbin/clog " . escapeshellarg($logfile) . " | grep -v \"CLOG\" | grep -v \"\033\" | /usr/bin/grep 'filterlog:' | /usr/bin/tail -r -n {$tail}", $logarr);
+ }
+
+ $filterlog = array();
+ $counter = 0;
+
+ $filterinterface = strtoupper($filterinterface);
+ foreach ($logarr as $logent) {
+ if ($counter >= $nentries) {
+ break;
+ }
+
+ $flent = parse_filter_line($logent);
+ if (!$filterinterface || ($filterinterface == $flent['interface'])) {
+ if ((($flent != "") && (!is_array($filtertext)) && (match_filter_line ($flent, $filtertext))) ||
+ (($flent != "") && (is_array($filtertext)) && (match_filter_field($flent, $filtertext)))) {
+ $counter++;
+ $filterlog[] = $flent;
+ }
+ }
+ }
+ /* Since the lines are in reverse order, flip them around if needed based on the user's preference */
+ return isset($config['syslog']['reverse']) ? $filterlog : array_reverse($filterlog);
+}
+
+function escape_filter_regex($filtertext) {
+ /* If the caller (user) has not already put a backslash before a slash, to escape it in the regex, */
+ /* then this will do it. Take out any "\/" already there, then turn all ordinary "/" into "\/". */
+ return str_replace('/', '\/', str_replace('\/', '/', $filtertext));
+}
+
+function match_filter_line($flent, $filtertext = "") {
+ if (!$filtertext) {
+ return true;
+ }
+ $filtertext = escape_filter_regex(str_replace(' ', '\s+', $filtertext));
+ return @preg_match("/{$filtertext}/i", implode(" ", array_values($flent)));
+}
+
+function match_filter_field($flent, $fields) {
+ foreach ($fields as $key => $field) {
+ if ($field == "All") {
+ continue;
+ }
+ if ((strpos($field, '!') === 0)) {
+ $field = substr($field, 1);
+ if (strtolower($key) == 'act') {
+ if (in_arrayi($flent[$key], explode(" ", $field))) {
+ return false;
+ }
+ } else {
+ $field_regex = escape_filter_regex($field);
+ if (@preg_match("/{$field_regex}/i", $flent[$key])) {
+ return false;
+ }
+ }
+ } else {
+ if (strtolower($key) == 'act') {
+ if (!in_arrayi($flent[$key], explode(" ", $field))) {
+ return false;
+ }
+ } else {
+ $field_regex = escape_filter_regex($field);
+ if (!@preg_match("/{$field_regex}/i", $flent[$key])) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+// Case Insensitive in_array function
+function in_arrayi($needle, $haystack) {
+ return in_array(strtolower($needle), array_map('strtolower', $haystack));
+}
+
+function parse_filter_line($line) {
+ global $config, $g;
+
+ $flent = array();
+ $log_split = "";
+
+ if (!preg_match("/(.*)\s(.*)\sfilterlog:\s(.*)$/", $line, $log_split)) {
+ return "";
+ }
+
+ list($all, $flent['time'], $host, $rule) = $log_split;
+
+ $rule_data = explode(",", $rule);
+ $field = 0;
+
+ $flent['rulenum'] = $rule_data[$field++];
+ $flent['subrulenum'] = $rule_data[$field++];
+ $flent['anchor'] = $rule_data[$field++];
+ $flent['tracker'] = $rule_data[$field++];
+ $flent['realint'] = $rule_data[$field++];
+ $flent['interface'] = convert_real_interface_to_friendly_descr($flent['realint']);
+ $flent['reason'] = $rule_data[$field++];
+ $flent['act'] = $rule_data[$field++];
+ $flent['direction'] = $rule_data[$field++];
+ $flent['version'] = $rule_data[$field++];
+
+ if ($flent['version'] == '4' || $flent['version'] == '6') {
+ if ($flent['version'] == '4') {
+ $flent['tos'] = $rule_data[$field++];
+ $flent['ecn'] = $rule_data[$field++];
+ $flent['ttl'] = $rule_data[$field++];
+ $flent['id'] = $rule_data[$field++];
+ $flent['offset'] = $rule_data[$field++];
+ $flent['flags'] = $rule_data[$field++];
+ $flent['protoid'] = $rule_data[$field++];
+ $flent['proto'] = strtoupper($rule_data[$field++]);
+ } else {
+ $flent['class'] = $rule_data[$field++];
+ $flent['flowlabel'] = $rule_data[$field++];
+ $flent['hlim'] = $rule_data[$field++];
+ $flent['proto'] = $rule_data[$field++];
+ $flent['protoid'] = $rule_data[$field++];
+ }
+
+ $flent['length'] = $rule_data[$field++];
+ $flent['srcip'] = $rule_data[$field++];
+ $flent['dstip'] = $rule_data[$field++];
+
+ if ($flent['protoid'] == '6' || $flent['protoid'] == '17') { // TCP or UDP
+ $flent['srcport'] = $rule_data[$field++];
+ $flent['dstport'] = $rule_data[$field++];
+
+ $flent['src'] = $flent['srcip'] . ':' . $flent['srcport'];
+ $flent['dst'] = $flent['dstip'] . ':' . $flent['dstport'];
+
+ $flent['datalen'] = $rule_data[$field++];
+ if ($flent['protoid'] == '6') { // TCP
+ $flent['tcpflags'] = $rule_data[$field++];
+ $flent['seq'] = $rule_data[$field++];
+ $flent['ack'] = $rule_data[$field++];
+ $flent['window'] = $rule_data[$field++];
+ $flent['urg'] = $rule_data[$field++];
+ $flent['options'] = explode(";", $rule_data[$field++]);
+ }
+ } else if ($flent['protoid'] == '1') { // ICMP
+ $flent['src'] = $flent['srcip'];
+ $flent['dst'] = $flent['dstip'];
+
+ $flent['icmp_type'] = $rule_data[$field++];
+
+ switch ($flent['icmp_type']) {
+ case "request":
+ case "reply":
+ $flent['icmp_id'] = $rule_data[$field++];
+ $flent['icmp_seq'] = $rule_data[$field++];
+ break;
+ case "unreachproto":
+ $flent['icmp_dstip'] = $rule_data[$field++];
+ $flent['icmp_protoid'] = $rule_data[$field++];
+ break;
+ case "unreachport":
+ $flent['icmp_dstip'] = $rule_data[$field++];
+ $flent['icmp_protoid'] = $rule_data[$field++];
+ $flent['icmp_port'] = $rule_data[$field++];
+ break;
+ case "unreach":
+ case "timexceed":
+ case "paramprob":
+ case "redirect":
+ case "maskreply":
+ $flent['icmp_descr'] = $rule_data[$field++];
+ break;
+ case "needfrag":
+ $flent['icmp_dstip'] = $rule_data[$field++];
+ $flent['icmp_mtu'] = $rule_data[$field++];
+ break;
+ case "tstamp":
+ $flent['icmp_id'] = $rule_data[$field++];
+ $flent['icmp_seq'] = $rule_data[$field++];
+ break;
+ case "tstampreply":
+ $flent['icmp_id'] = $rule_data[$field++];
+ $flent['icmp_seq'] = $rule_data[$field++];
+ $flent['icmp_otime'] = $rule_data[$field++];
+ $flent['icmp_rtime'] = $rule_data[$field++];
+ $flent['icmp_ttime'] = $rule_data[$field++];
+ break;
+ default :
+ $flent['icmp_descr'] = $rule_data[$field++];
+ break;
+ }
+
+ } else if ($flent['protoid'] == '2') { // IGMP
+ $flent['src'] = $flent['srcip'];
+ $flent['dst'] = $flent['dstip'];
+ } else if ($flent['protoid'] == '112') { // CARP
+ $flent['type'] = $rule_data[$field++];
+ $flent['ttl'] = $rule_data[$field++];
+ $flent['vhid'] = $rule_data[$field++];
+ $flent['version'] = $rule_data[$field++];
+ $flent['advskew'] = $rule_data[$field++];
+ $flent['advbase'] = $rule_data[$field++];
+ }
+ } else {
+ if ($g['debug']) {
+ log_error(sprintf(gettext("There was a error parsing rule number: %s. Please report to mailing list or forum."), $flent['rulenum']));
+ }
+ return "";
+ }
+
+ /* If there is a src, a dst, and a time, then the line should be usable/good */
+ if (!((trim($flent['src']) == "") || (trim($flent['dst']) == "") || (trim($flent['time']) == ""))) {
+ return $flent;
+ } else {
+ if ($g['debug']) {
+ log_error(sprintf(gettext("There was a error parsing rule: %s. Please report to mailing list or forum."), $errline));
+ }
+ return "";
+ }
+}
+
+function get_port_with_service($port, $proto) {
+ if (!$port) {
+ return '';
+ }
+
+ $service = getservbyport($port, $proto);
+ $portstr = "";
+ if ($service) {
+ $portstr = sprintf('<span title="' . gettext('Service %1$s/%2$s: %3$s') . '">' . htmlspecialchars($port) . '</span>', $port, $proto, $service);
+ } else {
+ $portstr = htmlspecialchars($port);
+ }
+ return ':' . $portstr;
+}
+
+function find_rule_by_number($rulenum, $trackernum, $type="block") {
+ global $g;
+
+ /* Passing arbitrary input to grep could be a Very Bad Thing(tm) */
+ if (!is_numeric($rulenum) || !is_numeric($trackernum) || !in_array($type, array('pass', 'block', 'match', 'rdr'))) {
+ return;
+ }
+
+ if ($trackernum == "0") {
+ $lookup_pattern = "^@{$rulenum}\([0-9]+\)[[:space:]]{$type}[[:space:]].*[[:space:]]log[[:space:]]";
+ } else {
+ $lookup_pattern = "^@[0-9]+\({$trackernum}\)[[:space:]]{$type}[[:space:]].*[[:space:]]log[[:space:]]";
+ }
+
+ /* At the moment, miniupnpd is the only thing I know of that
+ generates logging rdr rules */
+ unset($buffer);
+ if ($type == "rdr") {
+ $_gb = exec("/sbin/pfctl -vvPsn -a \"miniupnpd\" | /usr/bin/egrep " . escapeshellarg("^@{$rulenum}"), $buffer);
+ } else {
+ if (file_exists("{$g['tmp_path']}/rules.debug")) {
+ $_gb = exec("/sbin/pfctl -vvPnf {$g['tmp_path']}/rules.debug 2>/dev/null | /usr/bin/egrep " . escapeshellarg($lookup_pattern), $buffer);
+ } else {
+ $_gb = exec("/sbin/pfctl -vvPsr | /usr/bin/egrep " . escapeshellarg($lookup_pattern), $buffer);
+ }
+ }
+ if (is_array($buffer)) {
+ return $buffer[0];
+ }
+
+ return "";
+}
+
+function buffer_rules_load() {
+ global $g, $buffer_rules_rdr, $buffer_rules_normal;
+ unset($buffer, $buffer_rules_rdr, $buffer_rules_normal);
+ /* Redeclare globals after unset to work around PHP */
+ global $buffer_rules_rdr, $buffer_rules_normal;
+ $buffer_rules_rdr = array();
+ $buffer_rules_normal = array();
+
+ $_gb = exec("/sbin/pfctl -vvPsn -a \"miniupnpd\" | grep '^@'", $buffer);
+ if (is_array($buffer)) {
+ foreach ($buffer as $line) {
+ list($key, $value) = explode (" ", $line, 2);
+ $buffer_rules_rdr[$key] = $value;
+ }
+ }
+ unset($buffer, $_gb);
+ if (file_exists("{$g['tmp_path']}/rules.debug")) {
+ $_gb = exec("/sbin/pfctl -vvPnf {$g['tmp_path']}/rules.debug 2>/dev/null | /usr/bin/egrep '^@[0-9]+\([0-9]+\)[[:space:]].*[[:space:]]log[[:space:]]' | /usr/bin/egrep -v '^@[0-9]+\([0-9]+\)[[:space:]](nat|rdr|binat|no|scrub)'", $buffer);
+ } else {
+ $_gb = exec("/sbin/pfctl -vvPsr | /usr/bin/egrep '^@[0-9]+\([0-9]+\)[[:space:]].*[[:space:]]log[[:space:]]'", $buffer);
+ }
+
+ if (is_array($buffer)) {
+ foreach ($buffer as $line) {
+ list($key, $value) = explode (" ", $line, 2);
+ # pfctl rule number output with tracker number: @dd(dddddddddd)
+ $matches = array();
+ if (preg_match('/\@(?P<rulenum>\d+)\((?<trackernum>\d+)\)/', $key, $matches) == 1) {
+ if ($matches['trackernum'] > 0) {
+ $key = $matches['trackernum'];
+ } else {
+ $key = "@{$matches['rulenum']}";
+ }
+ }
+ $buffer_rules_normal[$key] = $value;
+ }
+ }
+ unset($_gb, $buffer);
+}
+
+function buffer_rules_clear() {
+ unset($GLOBALS['buffer_rules_normal']);
+ unset($GLOBALS['buffer_rules_rdr']);
+}
+
+function find_rule_by_number_buffer($rulenum, $trackernum, $type) {
+ global $g, $buffer_rules_rdr, $buffer_rules_normal;
+
+ if ($trackernum == "0") {
+ $lookup_key = "@{$rulenum}";
+ } else {
+ $lookup_key = $trackernum;
+ }
+
+ if ($type == "rdr") {
+ $ruleString = $buffer_rules_rdr[$lookup_key];
+ //TODO: get the correct 'description' part of a RDR log line. currently just first 30 characters..
+ $rulename = substr($ruleString, 0, 30);
+ } else {
+ $ruleString = $buffer_rules_normal[$lookup_key];
+ list(,$rulename,) = explode("\"", $ruleString);
+ $rulename = str_replace("USER_RULE: ", '<img src="/themes/' . $g['theme'] . '/images/icons/icon_frmfld_user.png" width="11" height="12" title="USER_RULE" alt="USER_RULE"/> ', $rulename);
+ }
+ return "{$rulename} ({$lookup_key})";
+}
+
+function find_action_image($action) {
+ global $g;
+ if ((strstr(strtolower($action), "p")) || (strtolower($action) == "rdr")) {
+ return "/themes/{$g['theme']}/images/icons/icon_pass.gif";
+ } else if (strstr(strtolower($action), "r")) {
+ return "/themes/{$g['theme']}/images/icons/icon_reject.gif";
+ } else {
+ return "/themes/{$g['theme']}/images/icons/icon_block.gif";
+ }
+}
+
+/* AJAX specific handlers */
+function handle_ajax($nentries, $tail = 50) {
+ global $config;
+ if ($_GET['lastsawtime'] or $_POST['lastsawtime']) {
+ global $filter_logfile, $filterent;
+ if ($_GET['lastsawtime']) {
+ $lastsawtime = $_GET['lastsawtime'];
+ }
+ if ($_POST['lastsawtime']) {
+ $lastsawtime = $_POST['lastsawtime'];
+ }
+ /* compare lastsawrule's time stamp to filter logs.
+ * afterwards return the newer records so that client
+ * can update AJAX interface screen.
+ */
+ $new_rules = "";
+ $filterlog = conv_log_filter($filter_logfile, $nentries, $tail);
+ /* We need this to always be in forward order for the AJAX update to work properly */
+ $filterlog = isset($config['syslog']['reverse']) ? array_reverse($filterlog) : $filterlog;
+ foreach ($filterlog as $log_row) {
+ $row_time = strtotime($log_row['time']);
+ $img = "<img border='0' src='" . find_action_image($log_row['act']) . "' alt={$log_row['act']} title={$log_row['act']} />";
+ if ($row_time > $lastsawtime) {
+ if ($log_row['proto'] == "TCP") {
+ $log_row['proto'] .= ":{$log_row['tcpflags']}";
+ }
+
+ $img = "<a href=\"#\" onClick=\"javascript:getURL('diag_logs_filter.php?getrulenum={$log_row['rulenum']},{$log_row['rulenum']}', outputrule);\">{$img}</a>";
+ $new_rules .= "{$img}||{$log_row['time']}||{$log_row['interface']}||{$log_row['srcip']}||{$log_row['srcport']}||{$log_row['dstip']}||{$log_row['dstport']}||{$log_row['proto']}||{$log_row['version']}||" . time() . "||\n";
+ }
+ }
+ echo $new_rules;
+ exit;
+ }
+}
+
+?>
diff --git a/src/etc/inc/functions.inc b/src/etc/inc/functions.inc
new file mode 100644
index 0000000..2c8f4c8
--- /dev/null
+++ b/src/etc/inc/functions.inc
@@ -0,0 +1,158 @@
+<?php
+/* $Id$ */
+/*
+ functions.inc
+ Copyright (C) 2004-2006 Scott Ullrich
+ All rights reserved.
+
+ originally part of m0n0wall (http://m0n0.ch/wall)
+ 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.
+
+ pfSense_MODULE: utils
+
+*/
+
+/* BEGIN compatibility goo with HEAD */
+if (!function_exists("gettext")) {
+ function gettext($text) {
+ return $text;
+ }
+}
+
+if (!function_exists("pfSenseHeader")) {
+ /****f* pfsense-utils/pfSenseHeader
+ * NAME
+ * pfSenseHeader
+ * INPUTS
+ * none
+ * RESULT
+ * Javascript header change or browser Location:
+ ******/
+ function pfSenseHeader($text) {
+ global $_SERVER;
+ if (isAjax()) {
+ if ($_SERVER['HTTPS'] == "on") {
+ $protocol = "https";
+ } else {
+ $protocol = "http";
+ }
+
+ $port = ":{$_SERVER['SERVER_PORT']}";
+ if ($_SERVER['SERVER_PORT'] == "80" && $protocol == "http") {
+ $port = "";
+ }
+ if ($_SERVER['SERVER_PORT'] == "443" && $protocol == "https") {
+ $port = "";
+ }
+ $complete_url = "{$protocol}://{$_SERVER['SERVER_NAME']}{$port}/{$text}";
+ echo "\ndocument.location.href = '{$complete_url}';\n";
+ } else {
+ header("Location: $text");
+ }
+ }
+}
+/* END compatibility goo with HEAD */
+
+/*fetch menu notices function*/
+if (!function_exists("get_menu_messages")) {
+ function get_menu_messages() {
+ global $g, $config;
+ if (are_notices_pending()) {
+ $notices = get_notices();
+ $requests = array();
+
+ ## Get Query Arguments from URL ###
+ foreach ($_REQUEST as $key => $value) {
+ if ($key != "PHPSESSID") {
+ $requests[] = $key.'='.$value;
+ }
+ }
+ if (is_array($requests)) {
+ $request_string = implode("&", $requests);
+ }
+
+ if (is_array($notices)) {
+ $notice_msgs = "<table colspan=\'6\' id=\'notice_table\'>";
+ $alert_style="style=\'color:#ffffff; filter:Glow(color=#ff0000, strength=12);\' ";
+ $notice = "<a href=\'#\' onclick=notice_action(\'acknowledge\',\'all\');domTT_close(this); {$alert_style}>".gettext("Acknowledge All Notices")."</a>";
+ $alert_link="title=\'".gettext("Click to Acknowledge")."\' {$alert_style}";
+ $domtt_width=500;
+ foreach ($notices as $key => $value) {
+ $date = date("m-d-y H:i:s", $key);
+ $noticemsg = ($value['notice'] != "" ? $value['notice'] : $value['id']);
+ $noticemsg = preg_replace("/(\"|\'|\n|<.?\w+>)/i", "", $noticemsg);
+ if ((strlen($noticemsg)* 8) > $domtt_width) {
+ $domtt_width=(strlen($noticemsg) *8);
+ }
+ if ((strlen($noticemsg)* 8) > 900) {
+ $domtt_width= 900;
+ }
+ $alert_action ="onclick=notice_action(\'acknowledge\',\'{$key}\');domTT_close(this);jQuery(this).parent().parent().remove();";
+ $notice_msgs .= "<tr><td valign=\'top\' width=\'120\'><a href=\'#\' {$alert_link} {$alert_action}>{$date}</a></td><td valign=\'top\'><a href=\'#\' {$alert_link} {$alert_action}>[ ".htmlspecialchars($noticemsg)."]</a></td></tr>";
+ }
+ $notice_msgs .="</table>";
+
+ $domtt= "onclick=\"domTT_activate(this, event, 'caption', '{$notice}','content', '<br />{$notice_msgs}', 'trail', false, 'delay', 0, 'fade', 'both', 'fadeMax', 93, 'styleClass', 'niceTitle','width','{$domtt_width}','y',5,'type', 'sticky');\"";
+ $menu_messages="<div id='alerts'>\n";
+ if (count($notices) == 1) {
+ $msg= sprintf("%1$02d", count($notices)) . " " . gettext("unread notice");
+ } else {
+ $msg= sprintf("%1$02d", count($notices)) . " " . gettext("unread notices");
+ }
+ $menu_messages .= "<div id='marquee-text' style='z-index:1001;'><a href='#' {$domtt}><b> .:. {$msg} .:. </b></a></div>\n";
+ $menu_messages .= "</div>\n";
+ }
+ } else {
+ $menu_messages = '<div id="hostname">';
+ $menu_messages .= $config['system']['hostname'] . "." . $config['system']['domain'];
+ $menu_messages .= '</div>';
+ }
+ return ($menu_messages);
+ }
+}
+
+if (!function_exists("dom_title")) {
+ function dom_title($title_msg, $width=NULL) {
+ $width=preg_replace("/\D+/", "", $width);
+ if (!empty($width)) {
+ $width=",'width',$width";
+ }
+ if (!empty($title_msg)) {
+ $title_msg=preg_replace("/\s+/", " ", $title_msg);
+ $title_msg=preg_replace("/'/", "\'", $title_msg);
+ return "onmouseout=\"this.style.color = ''; domTT_mouseout(this, event);\" onmouseover=\"domTT_activate(this, event, 'content', '{$title_msg}', 'trail', true, 'delay', 250, 'fade', 'both', 'fadeMax', 93, 'styleClass', 'niceTitle' $width);\"";
+ }
+ }
+}
+/* include all configuration functions */
+require_once("interfaces.inc");
+require_once("gwlb.inc");
+require_once("services.inc");
+require_once("pfsense-utils.inc");
+require_once("certs.inc");
+require_once("system.inc");
+require_once("vslb.inc");
+
+?>
diff --git a/src/etc/inc/globals.inc b/src/etc/inc/globals.inc
new file mode 100644
index 0000000..f7fe0fc
--- /dev/null
+++ b/src/etc/inc/globals.inc
@@ -0,0 +1,195 @@
+<?php
+/* $Id$ */
+/*
+ globals.inc
+ part of pfSense (https://www.pfsense.org)
+ Copyright (C) 2004-2010 Scott Ullrich
+
+ Originally Part of m0n0wall
+ 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.
+
+ pfSense_MODULE: utils
+
+*/
+
+global $g;
+$g = array(
+ "base_packages" => "siproxd",
+ "event_address" => "unix:///var/run/check_reload_status",
+ "factory_shipped_username" => "admin",
+ "factory_shipped_password" => "pfsense",
+ "upload_path" => "/root",
+ "dhcpd_chroot_path" => "/var/dhcpd",
+ "unbound_chroot_path" => "/var/unbound",
+ "var_path" => "/var",
+ "varrun_path" => "/var/run",
+ "varetc_path" => "/var/etc",
+ "vardb_path" => "/var/db",
+ "varlog_path" => "/var/log",
+ "etc_path" => "/etc",
+ "tmp_path" => "/tmp",
+ "conf_path" => "/conf",
+ "conf_default_path" => "/conf.default",
+ "cf_path" => "/cf",
+ "cf_conf_path" => "/cf/conf",
+ "www_path" => "/usr/local/www",
+ "xml_rootobj" => "pfsense",
+ "admin_group" => "admins",
+ "product_name" => "pfSense",
+ "product_version" => trim(file_get_contents("/etc/version"), " \n"),
+ "product_copyright" => "Electric Sheep Fencing LLC",
+ "product_copyright_url" => "http://www.electricsheepfencing.com",
+ "product_copyright_years" => "2004 - ".date("Y"),
+ "product_website" => "www.pfsense.org",
+ "product_website_footer" => "https://www.pfsense.org/?gui22",
+ "product_email" => "coreteam@pfsense.org",
+ "hideplatform" => false,
+ "hidedownloadbackup" => false,
+ "hidebackupbeforeupgrade" => false,
+ "disablethemeselection" => false,
+ "disablehelpmenu" => false,
+ "disablehelpicon" => false,
+ "disablecrashreporter" => false,
+ "crashreporterurl" => "https://crashreporter.pfsense.org/crash_reporter.php",
+ "debug" => false,
+ "latest_config" => "11.9",
+ "nopkg_platforms" => array("cdrom"),
+ "minimum_ram_warning" => "101",
+ "minimum_ram_warning_text" => "128 MB",
+ "wan_interface_name" => "wan",
+ "xmlrpcbaseurl" => "https://packages.pfsense.org",
+ "captiveportal_path" => "/usr/local/captiveportal",
+ "captiveportal_element_path" => "/var/db/cpelements",
+ "captiveportal_element_sizelimit" => 1048576,
+ "xmlrpcpath" => "/xmlrpc.php",
+ "embeddedbootupslice" => "/dev/ad0a",
+ "services_dhcp_server_enable" => true,
+ "wireless_regex" => "/^(ndis|wi|ath|an|ral|ural|iwi|wlan|rum|run|bwn|zyd|mwl|bwi|ipw|iwn|malo|uath|upgt|urtw|wpi)/",
+ "help_base_url" => "/help.php",
+ "pkg_prefix" => "pfSense-pkg-"
+);
+
+/* IP TOS flags */
+$iptos = array("lowdelay", "throughput", "reliability");
+
+/* TCP flags */
+$tcpflags = array("syn", "ack", "fin", "rst", "psh", "urg", "ece", "cwr");
+
+if (file_exists("/etc/platform")) {
+ $arch = php_uname("m");
+
+ if (strstr($g['product_version'], "-RELEASE")) {
+ /* This is only necessary for RELEASE */
+ $arch = ($arch == "i386") ? "" : '/' . $arch;
+ /* Full installs and NanoBSD use the same update directory and manifest in 2.x */
+ $g['update_url']="https://updates.pfsense.org/_updaters{$arch}";
+ $g['update_manifest']="https://updates.pfsense.org/manifest";
+ } else {
+ /* Full installs and NanoBSD use the same update directory and manifest in 2.x */
+ $g['update_url']="https://snapshots.pfsense.org/FreeBSD_releng/10.1/{$arch}/pfSense_HEAD/.updaters/";
+ $g['update_manifest']="https://updates.pfSense.org/manifest";
+ }
+
+ $g['platform'] = trim(file_get_contents("/etc/platform"));
+ if ($g['platform'] == "nanobsd") {
+ $g['firmware_update_text']="pfSense-*.img.gz";
+ $g['hidedownloadbackup'] = true;
+ $g['hidebackupbeforeupgrade'] = true;
+
+ } else {
+ $g['firmware_update_text']="pfSense-*.tgz";
+ }
+}
+
+/* Default sysctls */
+$sysctls = array("net.inet.ip.portrange.first" => "1024",
+ "net.inet.tcp.blackhole" => "2",
+ "net.inet.udp.blackhole" => "1",
+ "net.inet.ip.random_id" => "1",
+ "net.inet.tcp.drop_synfin" => "1",
+ "net.inet.ip.redirect" => "1",
+ "net.inet6.ip6.redirect" => "1",
+ "net.inet6.ip6.use_tempaddr" => "0",
+ "net.inet6.ip6.prefer_tempaddr" => "0",
+ "net.inet.tcp.syncookies" => "1",
+ "net.inet.tcp.recvspace" => "65228",
+ "net.inet.tcp.sendspace" => "65228",
+ "net.inet.ip.fastforwarding" => "0",
+ "net.inet.tcp.delayed_ack" => "0",
+ "net.inet.udp.maxdgram" => "57344",
+ "net.link.bridge.pfil_onlyip" => "0",
+ "net.link.bridge.pfil_member" => "1",
+ "net.link.bridge.pfil_bridge" => "0",
+ "net.link.tap.user_open" => "1",
+ "kern.randompid" => "347",
+ "net.inet.ip.intr_queue_maxlen" => "1000",
+ "hw.syscons.kbd_reboot" => "0",
+ "net.inet.tcp.log_debug" => "0",
+ "net.inet.tcp.tso" => "1",
+ "net.inet.icmp.icmplim" => "0",
+ "vfs.read_max" => "32",
+ "kern.ipc.maxsockbuf" => "4262144",
+ "net.inet.ip.process_options" => 0,
+ "kern.random.sys.harvest.interrupt" => 0,
+ "kern.random.sys.harvest.point_to_point" => 0,
+ "kern.random.sys.harvest.ethernet" => 0,
+ "net.route.netisr_maxqlen" => 1024,
+ "net.inet.udp.checksum" => 1,
+ "net.bpf.zerocopy_enable" => 1,
+ "net.inet.icmp.reply_from_interface" => 1,
+ "net.inet6.ip6.rfc6204w3" => 1,
+ "net.enc.out.ipsec_bpf_mask" => "0x0001",
+ "net.enc.out.ipsec_filter_mask" => "0x0001",
+ "net.enc.in.ipsec_bpf_mask" => "0x0002",
+ "net.enc.in.ipsec_filter_mask" => "0x0002",
+ "net.key.preferred_oldsa" => "0",
+ "net.inet.carp.senderr_demotion_factor" => 0, /* Do not demote CARP for interface send errors */
+ "net.pfsync.carp_demotion_factor" => 0 /* Do not demote CARP for pfsync errors */
+);
+
+/* Include override values for the above if needed. If the file doesn't exist, don't try to load it. */
+if (file_exists("/etc/inc/globals_override.inc")) {
+ @include("globals_override.inc");
+}
+
+function platform_booting($on_console = false) {
+ global $g;
+
+ if ($g['booting'] || file_exists("{$g['varrun_path']}/booting")) {
+ if ($on_console == false || php_sapi_name() != 'fpm-fcgi') {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+if (file_exists("{$g['cf_conf_path']}/enableserial_force")) {
+ $g['enableserial_force'] = true;
+}
+
+$config_parsed = false;
+
+?>
diff --git a/src/etc/inc/gmirror.inc b/src/etc/inc/gmirror.inc
new file mode 100644
index 0000000..9e26dfb
--- /dev/null
+++ b/src/etc/inc/gmirror.inc
@@ -0,0 +1,342 @@
+<?php
+/*
+ gmirror.inc
+ Copyright (C) 2009-2014 Jim Pingle
+ Copyright (C) 2013-2015 Electric Sheep Fencing, LP
+
+ 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.
+*/
+
+global $balance_methods;
+$balance_methods = array("load", "prefer", "round-robin", "split");
+
+/* Create a status array for each mirror and its disk components. */
+function gmirror_get_status() {
+ $status = "";
+ exec("/sbin/gmirror status -s", $status);
+ $mirrors = array();
+
+ /* Empty output = no mirrors found */
+ if (count($status) > 0) {
+ /* Loop through gmirror status output. */
+ foreach ($status as $line) {
+ /* Split the line by whitespace */
+ $all = preg_split("/[\s\t]+/", trim($line), 3);
+ if (count($all) == 3) {
+ /* If there are three items on a line, it is mirror name, status, and component */
+ $currentmirror = basename($all[0]);
+ $mirrors[$currentmirror]['name'] = basename($all[0]);
+ $mirrors[$currentmirror]['status'] = $all[1];
+ if (!is_array($mirrors[$currentmirror]['components'])) {
+ $mirrors[$currentmirror]['components'] = array();
+ }
+ $mirrors[$currentmirror]['components'][] = $all[2];
+ }
+ }
+ }
+ /* Return an hash of mirrors and components */
+ return $mirrors;
+}
+
+/* Get only status word for a single mirror. */
+function gmirror_get_status_single($mirror) {
+ $status = "";
+ $mirror_status = gmirror_get_status();
+ return $mirror_status[$mirror]['status'];
+}
+
+/* Generate an HTML formatted status for mirrors and disks in a small format for the widget */
+function gmirror_html_status() {
+ $mirrors = gmirror_get_status();
+ $output = "";
+ if (count($mirrors) > 0) {
+ $output .= "<tr>\n";
+ $output .= "<td width=\"40%\" class=\"vncellt\">Name</td>\n";
+ $output .= "<td width=\"40%\" class=\"vncellt\">Status</td>\n";
+ $output .= "<td width=\"20%\" class=\"vncellt\">Component</td>\n";
+ $output .= "</tr>\n";
+ foreach ($mirrors as $mirror => $name) {
+ $components = count($name["components"]);
+ $output .= "<tr>\n";
+ $output .= "<td width=\"40%\" rowspan=\"{$components}\" class=\"listr\">{$name['name']}</td>\n";
+ $output .= "<td width=\"40%\" rowspan=\"{$components}\" class=\"listr\">{$name['status']}</td>\n";
+ $output .= "<td width=\"20%\" class=\"listr\">{$name['components'][0]}</td>\n";
+ $output .= "</tr>\n";
+ if (count($name["components"]) > 1) {
+ $morecomponents = array_slice($name["components"], 1);
+ foreach ($morecomponents as $component) {
+ $output .= "<tr>\n";
+ $output .= "<td width=\"20%\" class=\"listr\">{$component}</td>\n";
+ $output .= "</tr>\n";
+ }
+ }
+ }
+ } else {
+ $output .= "<tr><td colspan=\"3\" class=\"listr\">No Mirrors Found</td></tr>\n";
+ }
+ // $output .= "<tr><td colspan=\"3\" class=\"listr\">Updated at " . date("F j, Y, g:i:s a") . "</td></tr>\n";
+ return $output;
+}
+
+/* List all disks in the system (potential gmirror targets) */
+function gmirror_get_disks() {
+ $disklist = "";
+ /* Get a list of disks in a scriptable way, exclude optical drives */
+ exec("/sbin/geom disk status -s | /usr/bin/grep -v '[[:blank:]]*cd[[:digit:]]*' | /usr/bin/awk '{print $1;}'", $disklist);
+ return $disklist;
+}
+
+/* List all potential gmirror consumers */
+function gmirror_get_unused_consumers() {
+ $consumerlist = "";
+ $disklist = gmirror_get_disks();
+ /* Get a list of consumers, exclude existing mirrors and diskid entries */
+ exec("/sbin/geom part status -s | /usr/bin/egrep -v '(mirror|diskid)' | /usr/bin/awk '{print $1, $3;}'", $consumerlist);
+ $all_consumers = array();
+ foreach ($consumerlist as $cl) {
+ $parts = explode(" ", $cl);
+ foreach ($parts as $part) {
+ $all_consumers[] = $part;
+ }
+ }
+ foreach ($disklist as $d) {
+ if (!is_consumer_used($d) && !in_array($d, $all_consumers)) {
+ $all_consumers[] = $d;
+ }
+ }
+ return $all_consumers;
+}
+
+/* List all existing geom mirrors */
+function gmirror_get_mirrors() {
+ $mirrorlist = "";
+ exec("/sbin/gmirror list | /usr/bin/grep '^Geom name:' | /usr/bin/awk '{print $3;}'", $mirrorlist);
+ return $mirrorlist;
+}
+
+
+/* List all consumers for a given mirror */
+function gmirror_get_consumers_in_mirror($mirror) {
+ if (!is_valid_mirror($mirror)) {
+ return array();
+ }
+
+ $consumers = array();
+ exec("/sbin/gmirror status -s " . escapeshellarg($mirror) . " | /usr/bin/awk '{print $3;}'", $consumers);
+ return $consumers;
+}
+
+/* Test if a given consumer is a member of an existing mirror */
+function is_consumer_in_mirror($consumer, $mirror) {
+ if (!is_valid_consumer($consumer) || !is_valid_mirror($mirror)) {
+ return false;
+ }
+
+ $mirrorconsumers = gmirror_get_consumers_in_mirror($mirror);
+ return in_array(basename($consumer), $mirrorconsumers);
+}
+
+/* Test if a mirror exists */
+function is_valid_mirror($mirror) {
+ $mirrors = gmirror_get_mirrors();
+ return in_array($mirror, $mirrors);
+}
+
+/* Test if a disk is valid/exists */
+function is_valid_disk($disk) {
+ $adisks = gmirror_get_disks();
+ return in_array(basename($disk), $adisks);
+}
+
+/* Test if a consumer is valid and in use in a mirror */
+function is_consumer_used($consumer) {
+ $found = false;
+ $mirrors = gmirror_get_mirrors();
+ foreach ($mirrors as $mirror) {
+ $consumers = gmirror_get_consumers_in_mirror($mirror);
+ if (in_array($consumer, $consumers)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Test if a consumer is valid and not in use */
+function is_consumer_unused($consumer) {
+ $consumers = gmirror_get_unused_consumers();
+ return in_array($consumer, $consumers);
+}
+
+/* Test if a consumer is valid (either a disk or partition) */
+function is_valid_consumer($consumer) {
+ return (is_consumer_unused($consumer) || is_consumer_used($consumer));
+}
+
+/* Remove all disconnected drives from a mirror */
+function gmirror_forget_disconnected($mirror) {
+ if (!is_valid_mirror($mirror)) {
+ return false;
+ }
+ return mwexec("/sbin/gmirror forget " . escapeshellarg($mirror));
+}
+
+/* Insert another consumer into a mirror */
+function gmirror_insert_consumer($mirror, $consumer) {
+ if (!is_valid_mirror($mirror) || !is_valid_consumer($consumer)) {
+ return false;
+ }
+ return mwexec("/sbin/gmirror insert " . escapeshellarg($mirror) . " " . escapeshellarg($consumer));
+}
+
+/* Remove consumer from a mirror and clear its metadata */
+function gmirror_remove_consumer($mirror, $consumer) {
+ if (!is_valid_mirror($mirror) || !is_valid_consumer($consumer)) {
+ return false;
+ }
+ return mwexec("/sbin/gmirror remove " . escapeshellarg($mirror) . " " . escapeshellarg($consumer));
+}
+
+/* Wipe geom info from drive (if mirror is not running) */
+function gmirror_clear_consumer($consumer) {
+ if (!is_valid_consumer($consumer)) {
+ return false;
+ }
+ return mwexec("/sbin/gmirror clear " . escapeshellarg($consumer));
+}
+
+/* Find the balance method used by a given mirror */
+function gmirror_get_mirror_balance($mirror) {
+ if (!is_valid_mirror($mirror)) {
+ return false;
+ }
+ $balancemethod = "";
+ exec("/sbin/gmirror list " . escapeshellarg($mirror) . " | /usr/bin/grep '^Balance:' | /usr/bin/awk '{print $2;}'", $balancemethod);
+ return $balancemethod[0];
+}
+
+/* Change balance algorithm of the mirror */
+function gmirror_configure_balance($mirror, $balancemethod) {
+ global $balance_methods;
+ if (!is_valid_mirror($mirror) || !in_array($balancemethod, $balance_methods)) {
+ return false;
+ }
+ return mwexec("/sbin/gmirror configure -b " . escapeshellarg($balancemethod) . " " . escapeshellarg($mirror));
+}
+
+/* Force a mirror member to rebuild */
+function gmirror_force_rebuild($mirror, $consumer) {
+ if (!is_valid_mirror($mirror) || !is_valid_consumer($consumer)) {
+ return false;
+ }
+ return mwexec("/sbin/gmirror rebuild " . escapeshellarg($mirror) . " " . escapeshellarg($consumer));
+}
+
+/* Show all metadata on the physical consumer */
+function gmirror_get_consumer_metadata($consumer) {
+ if (!is_valid_consumer($consumer)) {
+ return array();
+ }
+ $output = "";
+ exec("/sbin/gmirror dump " . escapeshellarg($consumer), $output);
+ return array_map('trim', $output);
+}
+
+/* Test if a consumer has metadata, indicating it is a member of a mirror (active or inactive) */
+function gmirror_consumer_has_metadata($consumer) {
+ return (count(gmirror_get_consumer_metadata($consumer)) > 0);
+}
+
+/* Find the mirror to which this consumer belongs */
+function gmirror_get_consumer_metadata_mirror($consumer) {
+ if (!is_valid_consumer($consumer)) {
+ return array();
+ }
+ $metadata = gmirror_get_consumer_metadata($consumer);
+ foreach ($metadata as $line) {
+ if (substr($line, 0, 5) == "name:") {
+ list ($key, $value) = explode(":", $line, 2);
+ return trim($value);
+ }
+ }
+}
+
+/* Deactivate consumer, removing it from service in the mirror, but leave metadata intact */
+function gmirror_deactivate_consumer($mirror, $consumer) {
+ if (!is_valid_mirror($mirror) || !is_valid_consumer($consumer)) {
+ return false;
+ }
+ return mwexec("/sbin/gmirror deactivate " . escapeshellarg($mirror) . " " . escapeshellarg($consumer));
+}
+
+/* Reactivate a deactivated consumer */
+function gmirror_activate_consumer($mirror, $consumer) {
+ if (!is_valid_mirror($mirror) || !is_valid_consumer($consumer)) {
+ return false;
+ }
+ return mwexec("/sbin/gmirror activate " . escapeshellarg($mirror) . " " . escapeshellarg($consumer));
+}
+
+/* Find the size of the given mirror */
+function gmirror_get_mirror_size($mirror) {
+ if (!is_valid_mirror($mirror)) {
+ return false;
+ }
+ $mirrorsize = "";
+ exec("/sbin/gmirror list " . escapeshellarg($mirror) . " | /usr/bin/grep 'Mediasize:' | /usr/bin/head -n 1 | /usr/bin/awk '{print $2;}'", $mirrorsize);
+ return $mirrorsize[0];
+}
+
+/* Return a list of all potential consumers on a disk with sizes. The geom part
+ list output is a little odd, we can't get the output for just the disk, if the disk contains
+ slices those get output also. */
+function gmirror_get_all_unused_consumer_sizes_on_disk($disk) {
+ if (!is_valid_disk($disk) || !is_consumer_unused($disk)) {
+ return array();
+ }
+ $output = "";
+ exec("/sbin/geom part list " . escapeshellarg($disk) . " | /usr/bin/egrep '(Name:|Mediasize:)' | /usr/bin/cut -c4- | /usr/bin/sed -l -e 'N;s/\\nMediasize://;P;D;' | /usr/bin/cut -c7-", $output);
+ if (empty($output)) {
+ exec("/sbin/geom disk list " . escapeshellarg($disk) . " | /usr/bin/egrep '(Name:|Mediasize:)' | /usr/bin/cut -c4- | /usr/bin/sed -l -e 'N;s/\\nMediasize://;P;D;' | /usr/bin/cut -c7-", $output);
+ }
+ $disk_contents = array();
+ foreach ($output as $line) {
+ list($name, $size, $humansize) = explode(" ", $line, 3);
+ $consumer = array();
+ $consumer['name'] = $name;
+ $consumer['size'] = $size;
+ $consumer['humansize'] = $humansize;
+ $disk_contents[] = $consumer;
+ }
+ return $disk_contents;
+}
+
+/* Get only the size for one specific potential consumer. */
+function gmirror_get_unused_consumer_size($consumer) {
+ $consumersizes = gmirror_get_all_unused_consumer_sizes_on_disk($consumer);
+ foreach ($consumersizes as $csize) {
+ if ($csize['name'] == $consumer) {
+ return $csize['size'];
+ }
+ }
+ return -1;
+}
+?>
diff --git a/src/etc/inc/growl.class b/src/etc/inc/growl.class
new file mode 100644
index 0000000..8f639e5
--- /dev/null
+++ b/src/etc/inc/growl.class
@@ -0,0 +1,102 @@
+<?PHP
+/*
+ pfSense_MODULE: notifications
+*/
+
+ class Growl
+ {
+ const GROWL_PRIORITY_LOW = -2;
+ const GROWL_PRIORITY_MODERATE = -1;
+ const GROWL_PRIORITY_NORMAL = 0;
+ const GROWL_PRIORITY_HIGH = 1;
+ const GROWL_PRIORITY_EMERGENCY = 2;
+
+ private $appName;
+ private $address;
+ private $notifications;
+ private $password;
+ private $port;
+
+ public function __construct($address, $password = '', $app_name = 'PHP-Growl')
+ {
+ $this->appName = utf8_encode($app_name);
+ $this->address = $address;
+ $this->notifications = array();
+ $this->password = $password;
+ $this->port = 9887;
+ }
+
+ public function addNotification($name, $enabled = true)
+ {
+ $this->notifications[] = array('name' => utf8_encode($name), 'enabled' => $enabled);
+ }
+
+ public function register()
+ {
+ $data = '';
+ $defaults = '';
+ $num_defaults = 0;
+
+ for ($i = 0; $i < count($this->notifications); $i++)
+ {
+ $data .= pack('n', strlen($this->notifications[$i]['name'])) . $this->notifications[$i]['name'];
+ if ($this->notifications[$i]['enabled'])
+ {
+ $defaults .= pack('c', $i);
+ $num_defaults++;
+ }
+ }
+
+ // pack(Protocol version, type, app name, number of notifications to register)
+ $data = pack('c2nc2', 1, 0, strlen($this->appName), count($this->notifications), $num_defaults) . $this->appName . $data . $defaults;
+ $data .= pack('H32', md5($data . $this->password));
+
+ return $this->send($data);
+ }
+
+ public function notify($name, $title, $message, $priority = 0, $sticky = false)
+ {
+ $name = utf8_encode($name);
+ $title = utf8_encode($title);
+ $message = utf8_encode($message);
+ $priority = intval($priority);
+
+ $flags = ($priority & 7) * 2;
+ if ($priority < 0) $flags |= 8;
+ if ($sticky) $flags |= 1;
+
+ // pack(protocol version, type, priority/sticky flags, notification name length, title length, message length. app name length)
+ $data = pack('c2n5', 1, 1, $flags, strlen($name), strlen($title), strlen($message), strlen($this->appName));
+ $data .= $name . $title . $message . $this->appName;
+ $data .= pack('H32', md5($data . $this->password));
+
+ return $this->send($data);
+ }
+
+ private function send($data)
+ {
+ if (function_exists('socket_create') && function_exists('socket_sendto'))
+ {
+ $sck = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
+ if ($sck) {
+ socket_sendto($sck, $data, strlen($data), 0x100, $this->address, $this->port);
+ return true;
+ }
+ }
+ elseif (function_exists('fsockopen'))
+ {
+ if ($this->address) {
+ $fp = @fsockopen('udp://' . $this->address, $this->port);
+ if ($fp) {
+ fwrite($fp, $data);
+ fclose($fp);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+ }
+
+?> \ No newline at end of file
diff --git a/src/etc/inc/gwlb.inc b/src/etc/inc/gwlb.inc
new file mode 100644
index 0000000..9880cdc
--- /dev/null
+++ b/src/etc/inc/gwlb.inc
@@ -0,0 +1,1252 @@
+<?php
+/*
+ gwlb.inc
+ Copyright (C) 2008 Bill Marquette, Seth Mos
+ Copyright (C) 2010 Ermal Luçi
+ 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.
+
+ pfSense_BUILDER_BINARIES: /sbin/route /usr/local/sbin/apinger
+ pfSense_MODULE: routing
+
+ */
+require_once("config.inc");
+require_once("rrd.inc");
+
+/* Returns an array of default values used for apinger.conf */
+function return_apinger_defaults() {
+ return array(
+ "latencylow" => "200",
+ "latencyhigh" => "500",
+ "losslow" => "10",
+ "losshigh" => "20",
+ "interval" => "1",
+ "down" => "10",
+ "avg_delay_samples" => "10",
+ "avg_loss_samples" => "50",
+ "avg_loss_delay_samples" => "20");
+}
+
+/*
+ * Creates monitoring configuration file and
+ * adds appropriate static routes.
+ */
+function setup_gateways_monitor() {
+ global $config, $g;
+
+ $gateways_arr = return_gateways_array();
+ if (!is_array($gateways_arr)) {
+ log_error("No gateways to monitor. Apinger will not be run.");
+ killbypid("{$g['varrun_path']}/apinger.pid");
+ @unlink("{$g['varrun_path']}/apinger.status");
+ return;
+ }
+
+ $apinger_debug = "";
+ if (isset($config['system']['apinger_debug'])) {
+ $apinger_debug = "debug on";
+ }
+
+ $apinger_default = return_apinger_defaults();
+ $apingerconfig = <<<EOD
+
+# pfSense apinger configuration file. Automatically Generated!
+
+{$apinger_debug}
+
+## User and group the pinger should run as
+user "root"
+group "wheel"
+
+## Mailer to use (default: "/usr/lib/sendmail -t")
+#mailer "/var/qmail/bin/qmail-inject"
+
+## Location of the pid-file (default: "/var/run/apinger.pid")
+pid_file "{$g['varrun_path']}/apinger.pid"
+
+## Format of timestamp (%s macro) (default: "%b %d %H:%M:%S")
+#timestamp_format "%Y%m%d%H%M%S"
+
+status {
+ ## File where the status information should be written to
+ file "{$g['varrun_path']}/apinger.status"
+ ## Interval between file updates
+ ## when 0 or not set, file is written only when SIGUSR1 is received
+ interval 5s
+}
+
+########################################
+# RRDTool status gathering configuration
+# Interval between RRD updates
+rrd interval 60s;
+
+## These parameters can be overridden in a specific alarm configuration
+alarm default {
+ command on "/usr/local/sbin/pfSctl -c 'service reload dyndns %T' -c 'service reload ipsecdns' -c 'service reload openvpn %T' -c 'filter reload' "
+ command off "/usr/local/sbin/pfSctl -c 'service reload dyndns %T' -c 'service reload ipsecdns' -c 'service reload openvpn %T' -c 'filter reload' "
+ combine 10s
+}
+
+## "Down" alarm definition.
+## This alarm will be fired when target doesn't respond for 30 seconds.
+alarm down "down" {
+ time {$apinger_default['down']}s
+}
+
+## "Delay" alarm definition.
+## This alarm will be fired when responses are delayed more than 200ms
+## it will be canceled, when the delay drops below 100ms
+alarm delay "delay" {
+ delay_low {$apinger_default['latencylow']}ms
+ delay_high {$apinger_default['latencyhigh']}ms
+}
+
+## "Loss" alarm definition.
+## This alarm will be fired when packet loss goes over 20%
+## it will be canceled, when the loss drops below 10%
+alarm loss "loss" {
+ percent_low {$apinger_default['losslow']}
+ percent_high {$apinger_default['losshigh']}
+}
+
+target default {
+ ## How often the probe should be sent
+ interval {$apinger_default['interval']}s
+
+ ## How many replies should be used to compute average delay
+ ## for controlling "delay" alarms
+ avg_delay_samples {$apinger_default['avg_delay_samples']}
+
+ ## How many probes should be used to compute average loss
+ avg_loss_samples {$apinger_default['avg_loss_samples']}
+
+ ## The delay (in samples) after which loss is computed
+ ## without this delays larger than interval would be treated as loss
+ avg_loss_delay_samples {$apinger_default['avg_loss_delay_samples']}
+
+ ## Names of the alarms that may be generated for the target
+ alarms "down","delay","loss"
+
+ ## Location of the RRD
+ #rrd file "{$g['vardb_path']}/rrd/apinger-%t.rrd"
+}
+
+EOD;
+
+ $monitor_ips = array();
+ foreach ($gateways_arr as $name => $gateway) {
+ /* Do not monitor if such was requested */
+ if (isset($gateway['monitor_disable'])) {
+ continue;
+ }
+ if (empty($gateway['monitor']) || !is_ipaddr($gateway['monitor'])) {
+ if (is_ipaddr($gateway['gateway'])) {
+ $gateway['monitor'] = $gateway['gateway'];
+ } else { /* No chance to get an ip to monitor skip target. */
+ continue;
+ }
+ }
+
+ /* if the monitor address is already used before, skip */
+ if (in_array($gateway['monitor'], $monitor_ips)) {
+ continue;
+ }
+
+ /* Interface ip is needed since apinger will bind a socket to it.
+ * However the config GUI should already have checked this and when
+ * PPoE is used the IP address is set to "dynamic". So using is_ipaddrv4
+ * or is_ipaddrv6 to identify packet type would be wrong, especially as
+ * further checks (that can cope with the "dynamic" case) are present inside
+ * the if block. So using $gateway['ipprotocol'] is the better option.
+ */
+ if ($gateway['ipprotocol'] == "inet") { // This is an IPv4 gateway...
+ $gwifip = find_interface_ip($gateway['interface'], true);
+ if (!is_ipaddrv4($gwifip)) {
+ continue; //Skip this target
+ }
+
+ if ($gwifip == "0.0.0.0") {
+ continue; //Skip this target - the gateway is still waiting for DHCP
+ }
+
+ /*
+ * If the gateway is the same as the monitor we do not add a
+ * route as this will break the routing table.
+ * Add static routes for each gateway with their monitor IP
+ * not strictly necessary but is a added level of protection.
+ */
+ if (is_ipaddrv4($gateway['gateway']) && $gateway['monitor'] != $gateway['gateway']) {
+ log_error("Removing static route for monitor {$gateway['monitor']} and adding a new route through {$gateway['gateway']}");
+ if (interface_isppp_type($gateway['friendlyiface'])) {
+ mwexec("/sbin/route change -host " . escapeshellarg($gateway['monitor']) .
+ " -iface " . escapeshellarg($gateway['interface']), true);
+ } else {
+ mwexec("/sbin/route change -host " . escapeshellarg($gateway['monitor']) .
+ " " . escapeshellarg($gateway['gateway']), true);
+ }
+
+ pfSense_kill_states("0.0.0.0/0", $gateway['monitor'], $gateway['interface'], "icmp");
+ }
+ } else if ($gateway['ipprotocol'] == "inet6") { // This is an IPv6 gateway...
+ if ($gateway['monitor'] == $gateway['gateway']) {
+ /* link locals really need a different src ip */
+ if (is_linklocal($gateway['gateway'])) {
+ if (!strpos($gateway['gateway'], '%')) {
+ $gateway['gateway'] .= '%' . $gateway['interface'];
+ }
+ $gwifip = find_interface_ipv6_ll($gateway['interface'], true);
+ } else {
+ $gwifip = find_interface_ipv6($gateway['interface'], true);
+ }
+ } else {
+ /* 'monitor' has been set, so makes sure it has precedence over
+ * 'gateway' in defining the source IP. Otherwise if 'gateway'
+ * is a local link and 'monitor' is global routable then the
+ * ICMP6 response would not find its way back home...
+ */
+ $gwifip = find_interface_ipv6($gateway['interface'], true);
+ }
+
+ /* Make sure srcip and target have scope defined when they are ll */
+ if (is_linklocal($gwifip) && !strpos($gwifip, '%')) {
+ $gwifip .= '%' . $gateway['interface'];
+ }
+ if (is_linklocal($gateway['monitor']) && !strpos($gateway['monitor'], '%')) {
+ $gateway['monitor'] .= "%{$gateway['interface']}";
+ }
+
+ if (!is_ipaddrv6($gwifip)) {
+ continue; //Skip this target
+ }
+
+ /*
+ * If the gateway is the same as the monitor we do not add a
+ * route as this will break the routing table.
+ * Add static routes for each gateway with their monitor IP
+ * not strictly necessary but is a added level of protection.
+ */
+ if ($gateway['gateway'] != $gateway['monitor']) {
+ log_error("Removing static route for monitor {$gateway['monitor']} and adding a new route through {$gateway['gateway']}");
+ if (interface_isppp_type($gateway['friendlyiface'])) {
+ mwexec("/sbin/route change -host -inet6 " . escapeshellarg($gateway['monitor']) .
+ " -iface " . escapeshellarg($gateway['interface']), true);
+ } else {
+ mwexec("/sbin/route change -host -inet6 " . escapeshellarg($gateway['monitor']) .
+ " " . escapeshellarg($gateway['gateway']), true);
+ }
+
+ pfSense_kill_states("::0.0.0.0/0", $gateway['monitor'], $gateway['interface'], "icmpv6");
+ }
+ } else {
+ continue;
+ }
+
+ $monitor_ips[] = $gateway['monitor'];
+ $apingercfg = "target \"{$gateway['monitor']}\" {\n";
+ $apingercfg .= " description \"{$name}\"\n";
+ $apingercfg .= " srcip \"{$gwifip}\"\n";
+
+ ## How often the probe should be sent
+ if (!empty($gateway['interval']) && is_numeric($gateway['interval'])) {
+ $interval = intval($gateway['interval']); # Restrict to Integer
+ if ($interval < 1) {
+ $interval = 1; # Minimum
+ }
+ if ($interval != $apinger_default['interval']) { # If not default value
+ $apingercfg .= " interval " . $interval . "s\n";
+ }
+ }
+
+ ## How many replies should be used to compute average delay
+ ## for controlling "delay" alarms
+ if (!empty($gateway['avg_delay_samples']) && is_numeric($gateway['avg_delay_samples'])) {
+ $avg_delay_samples = intval($gateway['avg_delay_samples']); # Restrict to Integer
+ if ($avg_delay_samples < 1) {
+ $avg_delay_samples = 1; # Minimum
+ }
+ if ($avg_delay_samples != $apinger_default['avg_delay_samples']) { # If not default value
+ $apingercfg .= " avg_delay_samples " . $avg_delay_samples . "\n";
+ }
+ }
+
+ ## How many probes should be used to compute average loss
+ if (!empty($gateway['avg_loss_samples']) && is_numeric($gateway['avg_loss_samples'])) {
+ $avg_loss_samples = intval($gateway['avg_loss_samples']); # Restrict to Integer
+ if ($avg_loss_samples < 1) {
+ $avg_loss_samples = 1; # Minimum
+ }
+ if ($avg_loss_samples != $apinger_default['avg_loss_samples']) { # If not default value
+ $apingercfg .= " avg_loss_samples " . $avg_loss_samples . "\n";
+ }
+ }
+
+ ## The delay (in samples) after which loss is computed
+ ## without this delays larger than interval would be treated as loss
+ if (!empty($gateway['avg_loss_delay_samples']) && is_numeric($gateway['avg_loss_delay_samples'])) {
+ $avg_loss_delay_samples = intval($gateway['avg_loss_delay_samples']); # Restrict to Integer
+ if ($avg_loss_delay_samples < 1) {
+ $avg_loss_delay_samples = 1; # Minimum
+ }
+ if ($avg_loss_delay_samples != $apinger_default['avg_loss_delay_samples']) { # If not default value
+ $apingercfg .= " avg_loss_delay_samples " . $avg_loss_delay_samples . "\n";
+ }
+ }
+
+ $alarms = "";
+ $alarmscfg = "";
+ $override = false;
+ if (!empty($gateway['losslow'])) {
+ $alarmscfg .= "alarm loss \"{$name}loss\" {\n";
+ $alarmscfg .= "\tpercent_low {$gateway['losslow']}\n";
+ $alarmscfg .= "\tpercent_high {$gateway['losshigh']}\n";
+ $alarmscfg .= "}\n";
+ $alarms .= "\"{$name}loss\"";
+ $override = true;
+ } else {
+ if ($override == true) {
+ $alarms .= ",";
+ }
+ $alarms .= "\"loss\"";
+ $override = true;
+ }
+ if (!empty($gateway['latencylow'])) {
+ $alarmscfg .= "alarm delay \"{$name}delay\" {\n";
+ $alarmscfg .= "\tdelay_low {$gateway['latencylow']}ms\n";
+ $alarmscfg .= "\tdelay_high {$gateway['latencyhigh']}ms\n";
+ $alarmscfg .= "}\n";
+ if ($override == true) {
+ $alarms .= ",";
+ }
+ $alarms .= "\"{$name}delay\"";
+ $override = true;
+ } else {
+ if ($override == true) {
+ $alarms .= ",";
+ }
+ $alarms .= "\"delay\"";
+ $override = true;
+ }
+ if (!empty($gateway['down'])) {
+ $alarmscfg .= "alarm down \"{$name}down\" {\n";
+ $alarmscfg .= "\ttime {$gateway['down']}s\n";
+ $alarmscfg .= "}\n";
+ if ($override == true) {
+ $alarms .= ",";
+ }
+ $alarms .= "\"{$name}down\"";
+ $override = true;
+ } else {
+ if ($override == true) {
+ $alarms .= ",";
+ }
+ $alarms .= "\"down\"";
+ $override = true;
+ }
+ if ($override == true) {
+ $apingercfg .= "\talarms override {$alarms};\n";
+ }
+
+ if (isset($gateway['force_down'])) {
+ $apingercfg .= "\tforce_down on\n";
+ }
+
+ $apingercfg .= " rrd file \"{$g['vardb_path']}/rrd/{$gateway['name']}-quality.rrd\"\n";
+ $apingercfg .= "}\n";
+ $apingercfg .= "\n";
+
+ $apingerconfig .= $alarmscfg;
+ $apingerconfig .= $apingercfg;
+