diff --git a/lib/Froxlor/Api/Commands/IpsAndPorts.php b/lib/Froxlor/Api/Commands/IpsAndPorts.php index b0a9f05e..801d6f7c 100644 --- a/lib/Froxlor/Api/Commands/IpsAndPorts.php +++ b/lib/Froxlor/Api/Commands/IpsAndPorts.php @@ -135,7 +135,7 @@ class IpsAndPorts extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\Resour { if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { - $ip = \Froxlor\Validate\Validate::validate_ip2($this->getParam('ip'), false, 'invalidip', false, false, false, true); + $ip = \Froxlor\Validate\Validate::validate_ip2($this->getParam('ip'), false, 'invalidip', false, false, false, false, true); $port = \Froxlor\Validate\Validate::validate($this->getParam('port', true, 80), 'port', '/^(([1-9])|([1-9][0-9])|([1-9][0-9][0-9])|([1-9][0-9][0-9][0-9])|([1-5][0-9][0-9][0-9][0-9])|(6[0-4][0-9][0-9][0-9])|(65[0-4][0-9][0-9])|(655[0-2][0-9])|(6553[0-5]))$/Di', array( 'stringisempty', 'myport' @@ -332,7 +332,7 @@ class IpsAndPorts extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\Resour 'id' => $id )); - $ip = \Froxlor\Validate\Validate::validate_ip2($this->getParam('ip', true, $result['ip']), false, 'invalidip', false, false, false, true); + $ip = \Froxlor\Validate\Validate::validate_ip2($this->getParam('ip', true, $result['ip']), false, 'invalidip', false, false, false, false, true); $port = \Froxlor\Validate\Validate::validate($this->getParam('port', true, $result['port']), 'port', '/^(([1-9])|([1-9][0-9])|([1-9][0-9][0-9])|([1-9][0-9][0-9][0-9])|([1-5][0-9][0-9][0-9][0-9])|(6[0-4][0-9][0-9][0-9])|(65[0-4][0-9][0-9])|(655[0-2][0-9])|(6553[0-5]))$/Di', array( 'stringisempty', 'myport' diff --git a/lib/Froxlor/Settings/Store.php b/lib/Froxlor/Settings/Store.php index baaf3a93..56bb5fed 100644 --- a/lib/Froxlor/Settings/Store.php +++ b/lib/Froxlor/Settings/Store.php @@ -252,6 +252,28 @@ class Store public static function storeSettingMysqlAccessHost($fieldname, $fielddata, $newfieldvalue) { + $ips = $newfieldvalue; + //Convert cidr to netmask for mysql, if needed be + if(strpos($ips, ',') !== false) { + $ips = explode(',', $ips); + } + if(is_array($ips) && count($ips) > 0) { + $newfieldvalue = []; + foreach ($ips as $ip) { + $org_ip = $ip; + $ip_cidr = explode("/", $ip); + if (count($ip_cidr) === 2) { + $ip = $ip_cidr[0]; + if (strlen($ip_cidr[1]) <= 2) { + $ip_cidr[1] = \Froxlor\Validate\Validate::cidr2NetmaskAddr($org_ip); + } + $newfieldvalue[] = $ip . '/' . $ip_cidr[1]; + } else { + $newfieldvalue[] = $org_ip; + } + } + $newfieldvalue = implode(',', $newfieldvalue); + } $returnvalue = self::storeSettingField($fieldname, $fielddata, $newfieldvalue); if ($returnvalue !== false && is_array($fielddata) && isset($fielddata['settinggroup']) && $fielddata['settinggroup'] == 'system' && isset($fielddata['varname']) && $fielddata['varname'] == 'mysql_access_host') { diff --git a/lib/Froxlor/Validate/Check.php b/lib/Froxlor/Validate/Check.php index d436616e..430e2e86 100644 --- a/lib/Froxlor/Validate/Check.php +++ b/lib/Froxlor/Validate/Check.php @@ -77,8 +77,7 @@ class Check $mysql_access_host_array = array_map('trim', explode(',', $newfieldvalue)); foreach ($mysql_access_host_array as $host_entry) { - - if (Validate::validate_ip2($host_entry, true, 'invalidip', true, true) == false && Validate::validateDomain($host_entry) == false && Validate::validateLocalHostname($host_entry) == false && $host_entry != '%') { + if (Validate::validate_ip2($host_entry, true, 'invalidip', true, true, true, true, false) == false && Validate::validateDomain($host_entry) == false && Validate::validateLocalHostname($host_entry) == false && $host_entry != '%') { return array( self::FORMFIELDS_PLAUSIBILITY_CHECK_ERROR, 'invalidmysqlhost', diff --git a/lib/Froxlor/Validate/Validate.php b/lib/Froxlor/Validate/Validate.php index 19020735..2eb20ffa 100644 --- a/lib/Froxlor/Validate/Validate.php +++ b/lib/Froxlor/Validate/Validate.php @@ -16,10 +16,10 @@ class Validate * @param * string language id for the error * @return string the clean string - * + * * If the default pattern is used and the string does not match, we try to replace the * 'bad' values and log the action. - * + * */ public static function validate($str, $fieldname, $pattern = '', $lng = '', $emptydefault = array(), $throw_exception = false) { @@ -62,32 +62,78 @@ class Validate \Froxlor\UI\Response::standard_error($lng, $fieldname, $throw_exception); } - /** - * Checks whether it is a valid ip - * - * @param string $ip - * ip-address to check - * @param bool $return_bool - * whether to return bool or call \Froxlor\UI\Response::standard_error() - * @param string $lng - * index for error-message (if $return_bool is false) - * @param bool $allow_localhost - * whether to allow 127.0.0.1 - * @param bool $allow_priv - * whether to allow private network addresses - * @param bool $allow_cidr - * whether to allow CIDR values e.g. 10.10.10.10/16 - * - * @return string|bool ip address on success, false on failure - */ - public static function validate_ip2($ip, $return_bool = false, $lng = 'invalidip', $allow_localhost = false, $allow_priv = false, $allow_cidr = false, $throw_exception = false) + /** + * Converts CIDR to a netmask address + * + * @thx to https://stackoverflow.com/a/5711080/3020926 + * @param string $cidr + * + * @return string + */ + public static function cidr2NetmaskAddr ($cidr) { + + $ta = substr ($cidr, strpos ($cidr, '/') + 1) * 1; + $netmask = str_split (str_pad (str_pad ('', $ta, '1'), 32, '0'), 8); + + foreach ($netmask as &$element) { + $element = bindec ($element); + } + + return implode ('.', $netmask); + } + + /** + * Checks if an $address (IP) is IPv6 + * + * @param $address + * + * @return bool + */ + public static function is_ipv6($address) { + return filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); + } + + /** + * Checks whether it is a valid ip + * + * @param string $ip + * ip-address to check + * @param bool $return_bool + * whether to return bool or call \Froxlor\UI\Response::standard_error() + * @param string $lng + * index for error-message (if $return_bool is false) + * @param bool $allow_localhost + * whether to allow 127.0.0.1 + * @param bool $allow_priv + * whether to allow private network addresses + * @param bool $allow_cidr + * whether to allow CIDR values e.g. 10.10.10.10/16 + * @param bool $cidr_as_netmask + * whether to format CIDR nodation to netmask notation + * + * @param bool $throw_exception + * whether to throw an exception on failure + * + * @return string|bool ip address on success, false on failure + */ + public static function validate_ip2($ip, $return_bool = false, $lng = 'invalidip', $allow_localhost = false, $allow_priv = false, $allow_cidr = false, $cidr_as_netmask = false, $throw_exception = false) { $cidr = ""; if ($allow_cidr) { $org_ip = $ip; $ip_cidr = explode("/", $ip); - if (count($ip_cidr) == 2) { + if (count($ip_cidr) === 2) { + if(strlen($ip_cidr[1]) <= 2 && in_array((int)$ip_cidr[1], array_values(range(1, 32)), TRUE) === false) { + \Froxlor\UI\Response::standard_error($lng, $ip, $throw_exception); + } + if ($cidr_as_netmask && self::is_ipv6($ip_cidr[0])) { + //MySQL does not handle CIDR of IPv6 addresses, return error + \Froxlor\UI\Response::standard_error($lng, $ip, $throw_exception); + } $ip = $ip_cidr[0]; + if ($cidr_as_netmask && strlen($ip_cidr[1]) <= 2) { + $ip_cidr[1] = self::cidr2NetmaskAddr($org_ip); + } $cidr = "/" . $ip_cidr[1]; } else { $ip = $org_ip; @@ -155,7 +201,7 @@ class Validate * The domainname which should be checked. * @param bool $allow_underscore * optional if true, allowes the underscore character in a domain label (DKIM etc.) - * + * * @return string|boolean the domain-name if the domain is valid, false otherwise */ public static function validateDomain($domainname, $allow_underscore = false) diff --git a/lng/english.lng.php b/lng/english.lng.php index 66e85231..c61cdd79 100644 --- a/lng/english.lng.php +++ b/lng/english.lng.php @@ -569,7 +569,7 @@ $lng['serversettings']['apacheconf_htpasswddir']['description'] = 'Where should $lng['error']['formtokencompromised'] = 'The request seems to be compromised. For security reasons you were logged out.'; $lng['serversettings']['mysql_access_host']['title'] = 'MySQL-Access-Hosts'; -$lng['serversettings']['mysql_access_host']['description'] = 'A comma separated list of hosts from which users should be allowed to connect to the MySQL-Server.'; +$lng['serversettings']['mysql_access_host']['description'] = 'A comma separated list of hosts from which users should be allowed to connect to the MySQL-Server. To allow a subnet the netmask or cidr syntax is valid.'; // ADDED IN 1.2.18-svn1 diff --git a/lng/german.lng.php b/lng/german.lng.php index 286a1a9b..b8d60d43 100644 --- a/lng/german.lng.php +++ b/lng/german.lng.php @@ -564,7 +564,7 @@ $lng['serversettings']['apacheconf_htpasswddir']['description'] = 'Wo sollen die $lng['error']['formtokencompromised'] = 'Das Formular scheint manipuliert worden zu sein. Aus Sicherheitsgründen wurden Sie ausgelogged.'; $lng['serversettings']['mysql_access_host']['title'] = 'MySQL-Access-Hosts'; -$lng['serversettings']['mysql_access_host']['description'] = 'Eine durch Komma getrennte Liste mit den Hostnamen aller Hostnames/IP-Adressen, von denen sich die Benutzer einloggen dürfen.'; +$lng['serversettings']['mysql_access_host']['description'] = 'Eine durch Komma getrennte Liste mit den Hostnamen aller Hostnames/IP-Adressen, von denen sich die Benutzer einloggen dürfen. Um ein Subnetz zu erlauben ist die Netzmaske oder CIDR Syntax erlaubt.'; // ADDED IN 1.2.18-svn1 diff --git a/tests/Froxlor/ValidateTest.php b/tests/Froxlor/ValidateTest.php index a47dba82..b825c057 100644 --- a/tests/Froxlor/ValidateTest.php +++ b/tests/Froxlor/ValidateTest.php @@ -50,7 +50,7 @@ class ValidateTest extends TestCase public function testValidateIp() { - $result = Validate::validate_ip2("12.34.56.78", false, 'invalidip', false, false, false, true); + $result = Validate::validate_ip2("12.34.56.78", false, 'invalidip', false, false, false, false, true); $this->assertEquals("12.34.56.78", $result); } @@ -58,12 +58,12 @@ class ValidateTest extends TestCase { $this->expectException("Exception"); $this->expectExceptionCode(400); - Validate::validate_ip2("10.0.0.1", false, 'invalidip', false, false, false, true); + Validate::validate_ip2("10.0.0.1", false, 'invalidip', false, false, false, false, true); } public function testValidateIpPrivNotAllowedBool() { - $result = Validate::validate_ip2("10.0.0.1", true, 'invalidip', false, false, false, true); + $result = Validate::validate_ip2("10.0.0.1", true, 'invalidip', false, false, false, false, true); $this->assertFalse($result); } @@ -71,32 +71,49 @@ class ValidateTest extends TestCase { $this->expectException("Exception"); $this->expectExceptionCode(400); - Validate::validate_ip2("12.34.56.78/24", false, 'invalidip', false, false, false, true); + Validate::validate_ip2("12.34.56.78/24", false, 'invalidip', false, false, false, false, true); } public function testValidateIpCidrNotAllowedBool() { - $result = Validate::validate_ip2("12.34.56.78/24", true, 'invalidip', false, false, false, true); + $result = Validate::validate_ip2("12.34.56.78/24", true, 'invalidip', false, false, false, false, true); $this->assertFalse($result); } public function testValidateIpCidr() { - $result = Validate::validate_ip2("12.34.56.78/24", false, 'invalidip', false, false, true, true); + $result = Validate::validate_ip2("12.34.56.78/24", false, 'invalidip', false, false, true, false, true); $this->assertEquals("12.34.56.78/24", $result); } + public function testValidateIpv6Disallowed() + { + $this->expectException("Exception"); + $this->expectExceptionCode(400); + Validate::validate_ip2("2620:0:2d0:200::7/32", false, 'invalidip', false, false, true, true, true); + } + public function testValidateIpLocalhostAllowed() { - $result = Validate::validate_ip2("127.0.0.1/32", false, 'invalidip', true, false, true, true); + $result = Validate::validate_ip2("127.0.0.1/32", false, 'invalidip', true, false, true, false, true); $this->assertEquals("127.0.0.1/32", $result); } + public function testValidateCidrNoationToNetmaskNotationIPv4() + { + $result = Validate::validate_ip2("1.1.1.1/4", false, 'invalidip', true, false, true, true, true); + $this->assertEquals("1.1.1.1/240.0.0.0", $result); + $result = Validate::validate_ip2("8.8.8.8/18", false, 'invalidip', true, false, true, true, true); + $this->assertEquals("8.8.8.8/255.255.192.0", $result); + $result = Validate::validate_ip2("8.8.8.8/1", false, 'invalidip', true, false, true, true, true); + $this->assertEquals("8.8.8.8/128.0.0.0", $result); + } + public function testValidateIpLocalhostAllowedWrongIp() { $this->expectException("Exception"); $this->expectExceptionCode(400); - Validate::validate_ip2("127.0.0.2", false, 'invalidip', true, false, false, true); + Validate::validate_ip2("127.0.0.2", false, 'invalidip', true, false, false, false, true); } public function testValidateUrl()