Commit 3539f80f authored by Chris Graham's avatar Chris Graham
Browse files

Fixed MANTIS-4312 (Wildcard IP bans not working)

parent 423fa2b9
Loading
Loading
Loading
Loading
+103 −0
Original line number Diff line number Diff line
<?php /*

 Composr
 Copyright (c) ocProducts, 2004-2016

 See text/EN/licence.txt for full licencing information.

*/

/**
 * @license    http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright  ocProducts Ltd
 * @package    testing_platform
 */

/**
 * Composr test case class (unit testing).
 */
class ip_addresses_test_set extends cms_test_case
{
    public function testFuncs()
    {
        require_code('failure');

        $this->assertTrue(!is_valid_ip('*.168.1.1'));
        $this->assertTrue(is_valid_ip('192.168.1.1', true));
        $this->assertTrue(!is_valid_ip('x.168.1.1', true));
        $this->assertTrue(is_valid_ip('192.168.1.*', true));
        $this->assertTrue(!is_valid_ip('*.168.1.1', true));
        $this->assertTrue(!is_valid_ip('*:db8::a00:20ff:fea7:ccea'));
        $this->assertTrue(is_valid_ip('2001:db8::a00:20ff:fea7:ccea', true));
        $this->assertTrue(!is_valid_ip('x:db8::a00:20ff:fea7:ccea', true));
        $this->assertTrue(!is_valid_ip('*:db8::a00:20ff:fea7:ccea', true));
        $this->assertTrue(is_valid_ip('db8::a00:20ff:fea7:ccea:*', true));

        $this->assertTrue(normalise_ip_address('192.168.1.1') == '192.168.1.1');
        $this->assertTrue(normalise_ip_address('192.168.1') == '192.168.1.0');
        $this->assertTrue(normalise_ip_address('192.168.1000') == ''); // Cannot normalise
        $this->assertTrue(normalise_ip_address('2001:db8::a00:20ff:fea7:ccea') == '2001:0DB8:0000:0000:0A00:20FF:FEA7:CCEA');
        $this->assertTrue(normalise_ip_address('2001:db8::20ff:fea7:ccea') == '2001:0DB8:0000:0000:0000:20FF:FEA7:CCEA');
        $this->assertTrue(normalise_ip_address('::1') == '0000:0000:0000:0000:0000:0000:0000:0001');

        $this->assertTrue(ip_wild_to_apache('192.168.1.1') == '192.168.1.1');
        $this->assertTrue(ip_wild_to_apache('2001:db8::a00:20ff:fea7:ccea') == '2001:0DB8:0000:0000:0A00:20FF:FEA7:CCEA');
        $this->assertTrue(ip_wild_to_apache('192.168.1.*') == '192.168.1.0/24');
        $this->assertTrue(ip_wild_to_apache('*.168.1.1') == ''); // Considered invalid, * must be on end
        $this->assertTrue(ip_wild_to_apache('f:db8::a00:20ff:fea7:*') == '000F:0DB8:0000:0000:0A00:20FF:FEA7:0000/112');
        $this->assertTrue(ip_wild_to_apache('*:f:db8::a00:20ff:fea7:') == ''); // Considered invalid, * must be on end

        $this->assertTrue(compare_ip_address('192.168.1.1', '192.168.1.1'));
        $this->assertTrue(!compare_ip_address('192.168.1.1', '192.168.1.2'));
        $this->assertTrue(compare_ip_address('2001:db8::a00:20ff:fea7:ccea', '2001:db8::a00:20ff:fea7:ccea'));
        $this->assertTrue(!compare_ip_address('2001:db8::a00:20ff:fea7:ccea', '2001:db8::a00:20ff:fea7:cceb'));
        $this->assertTrue(compare_ip_address('192.168.1.*', '192.168.1.1'));
        $this->assertTrue(!compare_ip_address('192.168.1.*', '192.168.2.1'));
        $this->assertTrue(compare_ip_address('192.168.1.*', '192.168.1.1'));
        $this->assertTrue(!compare_ip_address('192.168.1.*', '192.168.2.1'));
        $this->assertTrue(compare_ip_address('db8::a00:20ff:fea7:ccea:*', 'db8::a00:20ff:fea7:ccea:0000'));
        $this->assertTrue(!compare_ip_address('db8::a00:20ff:fea7:ccea:*', 'db8::a00:20ff:fea7:cceb:0000'));
    }

    public function testIPAddressSanitisation()
    {
        $expectations = [
            '' => false,

            'x' => false,

            '127.0.0.1' => true,
            '255.255.255.255' => true,
            '255.255.255.255.255' => false,
            '0.0.0.0' => true,
            '192.168.1' => false,
            '-0.0.0.0' => false,
            '-1.0.0.0' => false,
            '0.0.0.' => false,
            '.0.0.0' => false,
            ' 0.0.0.0' => false,
            '0.0.0.0 ' => false,
            '0.0.0 .0' => false,
            '0.0.0' => false,
            '0.0.0.0.0' => false,
            '256.256.256.256' => false,
            '1111.1111.1111.1111' => false,
            'a.a.a.a' => false,

            'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF' => true,
            'FFFF::FFFF:FFFF:FFFF:FFFF' => true, // Double colon allows shortening
            'FFFF::FFFF:FFFF::FFFF' => false, // Only 1 double colon allowed
            'FFFF::FFFF:1:FFFF:FFFF' => true,
            'A:0:1:2:3:4:5:6' => true, // Leading zeroes can be omitted
            '0000:0000:0000:0000:0000:0000:0000:0000' => true,
            'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' => true,
            'gggg:gggg:gggg:gggg:gggg:gggg:gggg:gggg' => false,
            'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF' => false,
            'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF' => false, // Too short for when no double colons
        ];

        foreach ($expectations as $string => $status) {
            $this->assertTrue(is_valid_ip($string) == $status, 'Incorrect IP address status for ' . $string);
        }
    }
}
+0 −38
Original line number Diff line number Diff line
@@ -64,42 +64,4 @@ class type_sanitisation_test_set extends cms_test_case
            $this->assertTrue(is_email_address($string) == $status, 'Incorrect e-mail address status for ' . $string);
        }
    }

    public function testIPAddressSanitisation()
    {
        $expectations = [
            '' => false,

            '127.0.0.1' => true,
            '255.255.255.255' => true,
            '0.0.0.0' => true,
            '-0.0.0.0' => false,
            '-1.0.0.0' => false,
            '0.0.0.' => false,
            '.0.0.0' => false,
            ' 0.0.0.0' => false,
            '0.0.0.0 ' => false,
            '0.0.0 .0' => false,
            '0.0.0' => false,
            '0.0.0.0.0' => false,
            '256.256.256.256' => false,
            '1111.1111.1111.1111' => false,
            'a.a.a.a' => false,

            'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF' => true,
            'FFFF::FFFF:FFFF:FFFF:FFFF' => true, // Double colon allows shortening
            'FFFF::FFFF:FFFF::FFFF' => false, // Only 1 double colon allowed
            'FFFF::FFFF:1:FFFF:FFFF' => true,
            'A:0:1:2:3:4:5:6' => true, // Leading zeroes can be omitted
            '0000:0000:0000:0000:0000:0000:0000:0000' => true,
            'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' => true,
            'gggg:gggg:gggg:gggg:gggg:gggg:gggg:gggg' => false,
            'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF' => false,
            'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF' => false, // Too short for when no double colons
        ];

        foreach ($expectations as $string => $status) {
            $this->assertTrue(is_ip_address($string) == $status, 'Incorrect IP address status for ' . $string);
        }
    }
}
+4 −4
Original line number Diff line number Diff line
@@ -277,12 +277,12 @@ class Module_admin_ip_ban
            if (trim($ban) == '') {
                continue;
            }
            if (preg_match('#^\s*([\d\.:A-F]+)(.*)$#i', $ban, $matches) == 0) {
            if (preg_match('#^\s*([\d\.:A-F\*]+)(.*)$#i', $ban, $matches) == 0) {
                $ip = $ban; // Will fail
            } else {
                $ip = $matches[1];
            }
            if (!is_ip_address($ip)) {
            if (!is_valid_ip($ip, true)) {
                attach_message(do_lang_tempcode('IP_ADDRESS_NOT_VALID', escape_html($ip)), 'warn');
            } else {
                if (!in_array($ip, $old_bans)) {
@@ -316,12 +316,12 @@ class Module_admin_ip_ban
            if (trim($str) == '') {
                continue;
            }
            if (preg_match('#^\s*([\d\.:A-F]+)(.*)$#i', $str, $matches) == 0) {
            if (preg_match('#^\s*([\d\.:A-F\*]+)(.*)$#i', $str, $matches) == 0) {
                $ip = $str; // Will fail
            } else {
                $ip = $matches[1];
            }
            if (!is_ip_address($ip)) {
            if (!is_valid_ip($ip, true)) {
                attach_message(do_lang_tempcode('IP_ADDRESS_NOT_VALID_MAKE_UNBANNABLE', escape_html($ip)), 'warn');
            } else {
                if (!in_array($ip, $unbannable_already)) {
+0 −0

File changed.

Preview suppressed by a .gitattributes entry or the file's encoding is unsupported.

+59 −9
Original line number Diff line number Diff line
@@ -755,7 +755,7 @@ function add_ip_ban($ip, $descrip = '', $ban_until = null, $ban_positive = true)
        return false;
    }
    require_code('type_sanitisation');
    if (!is_ip_address($ip)) {
    if (!is_valid_ip($ip, true)) {
        return false;
    }

@@ -769,10 +769,8 @@ function add_ip_ban($ip, $descrip = '', $ban_until = null, $ban_positive = true)
    persistent_cache_delete('IP_BANS');
    if ((is_writable_wrap(get_file_base() . '/.htaccess')) && (is_null($ban_until))) {
        $contents = unixify_line_format(cms_file_get_contents_safe(get_file_base() . '/.htaccess'));
        $ip_cleaned = str_replace('*', '', $ip);
        $ip_cleaned = str_replace('..', '.', $ip_cleaned);
        $ip_cleaned = str_replace('..', '.', $ip_cleaned);
        if ((stripos($contents, "\n" . 'deny from ' . $ip_cleaned) === false) && (stripos($contents, "\n" . 'require not ip ' . $ip_cleaned) === false)) {
        $ip_cleaned = ip_wild_to_apache($ip);
        if (($ip_cleaned != '') && (stripos($contents, "\n" . 'deny from ' . $ip_cleaned) === false) && (stripos($contents, "\n" . 'require not ip ' . $ip_cleaned) === false)) {
            require_code('files');

            // < Apache 2.4
@@ -788,6 +786,60 @@ function add_ip_ban($ip, $descrip = '', $ban_until = null, $ban_positive = true)
    return true;
}

/**
 * Convert simple Composr wildcard syntax in IP addresses to Apache netmask syntax.
 *
 * @param  IP $ip The IP address (potentially encoded with *'s)
 * @return string The Apache-style IP
 */
function ip_wild_to_apache($ip)
{
    $ip = normalise_ip_address($ip, 4);
    if ($ip == '') {
        return '';
    }

    if (strpos($ip, '*') === false) {
        return $ip;
    }

    $ipv6 = (strpos($ip, ':') !== false);
    if ($ipv6) {
        $delimiter = ':';
    } else {
        $delimiter = '.';
    }
    $parts = explode($delimiter, $ip);
    $ip_section = '';
    $range_bits = 0;
    foreach ($parts as $i => $part) {
        if ($i > 0) {
            $ip_section .= $delimiter;
        }
        if ($part == '*') {
            if ($ipv6) {
                $ip_section .= '0000';
            } else {
                $ip_section .= '0';
            }
        } else {
            $ip_section .= $part;
            if ($ipv6) {
                $range_bits += 16;
            } else {
                $range_bits += 8;
            }
        }
    }
    if ($ipv6) {
        while ($i < 7) {
            $range_bit .= ':FFFF';
            $i++;
        }
    }
    return $ip_section . '/' . strval($range_bits);
}

/**
 * Remove an IP-ban.
 *
@@ -803,10 +855,8 @@ function remove_ip_ban($ip)
    persistent_cache_delete('IP_BANS');
    if (is_writable_wrap(get_file_base() . '/.htaccess')) {
        $contents = unixify_line_format(cms_file_get_contents_safe(get_file_base() . '/.htaccess'));
        $ip_cleaned = str_replace('*', '', $ip);
        $ip_cleaned = str_replace('..', '.', $ip_cleaned);
        $ip_cleaned = str_replace('..', '.', $ip_cleaned);
        if (trim($ip_cleaned) != '') {
        $ip_cleaned = ip_wild_to_apache($ip);
        if ($ip_cleaned != '') {
            require_code('files');

            // < Apache 2.4
Loading