diff --git a/composer.lock b/composer.lock index ecf9b77d..ea9b6cb0 100644 --- a/composer.lock +++ b/composer.lock @@ -3066,16 +3066,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.20", + "version": "1.12.24", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "3240b1972042c7f73cf1045e879ea5bd5f761bb7" + "reference": "338b92068f58d9f8035b76aed6cf2b9e5624c025" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3240b1972042c7f73cf1045e879ea5bd5f761bb7", - "reference": "3240b1972042c7f73cf1045e879ea5bd5f761bb7", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/338b92068f58d9f8035b76aed6cf2b9e5624c025", + "reference": "338b92068f58d9f8035b76aed6cf2b9e5624c025", "shasum": "" }, "require": { @@ -3120,7 +3120,7 @@ "type": "github" } ], - "time": "2025-03-05T13:37:43+00:00" + "time": "2025-04-16T13:01:53+00:00" }, { "name": "phpunit/php-code-coverage", @@ -4571,16 +4571,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.11.3", + "version": "3.12.2", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "ba05f990e79cbe69b9f35c8c1ac8dca7eecc3a10" + "reference": "6d4cf6032d4b718f168c90a96e36c7d0eaacb2aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/ba05f990e79cbe69b9f35c8c1ac8dca7eecc3a10", - "reference": "ba05f990e79cbe69b9f35c8c1ac8dca7eecc3a10", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/6d4cf6032d4b718f168c90a96e36c7d0eaacb2aa", + "reference": "6d4cf6032d4b718f168c90a96e36c7d0eaacb2aa", "shasum": "" }, "require": { @@ -4647,11 +4647,11 @@ "type": "open_collective" }, { - "url": "https://thanks.dev/phpcsstandards", + "url": "https://thanks.dev/u/gh/phpcsstandards", "type": "thanks_dev" } ], - "time": "2025-01-23T17:04:15+00:00" + "time": "2025-04-13T04:10:18+00:00" }, { "name": "symfony/config", diff --git a/install/updates/froxlor/update_2.2.inc.php b/install/updates/froxlor/update_2.2.inc.php index 4c4529b8..04407ac6 100644 --- a/install/updates/froxlor/update_2.2.inc.php +++ b/install/updates/froxlor/update_2.2.inc.php @@ -226,7 +226,7 @@ if (Froxlor::isDatabaseVersion('202411200')) { $current_allowed_mysqlserver = !empty($customer['allowed_mysqlserver']) ? json_decode($customer['allowed_mysqlserver'], true) : []; foreach ($current_allowed_mysqlserver as $dbserver) { // require privileged access for target db-server - Database::needRoot(true, $dbserver, true); + Database::needRoot(true, $dbserver, false); // get DbManager $dbm = new DbManager(FroxlorLogger::getInstanceOf()); foreach (array_map('trim', explode(',', Settings::Get('system.mysql_access_host'))) as $mysql_access_host) { @@ -238,7 +238,7 @@ if (Froxlor::isDatabaseVersion('202411200')) { } } $dbm->getManager()->flushPrivileges(); - Database::needRoot(false); + Database::needRoot(); } } Update::lastStepStatus(0); diff --git a/lib/Froxlor/Api/ApiParameter.php b/lib/Froxlor/Api/ApiParameter.php index fdefb549..588579e6 100644 --- a/lib/Froxlor/Api/ApiParameter.php +++ b/lib/Froxlor/Api/ApiParameter.php @@ -44,7 +44,7 @@ abstract class ApiParameter * * @throws Exception */ - public function __construct(array $params = null) + public function __construct(?array $params = null) { if (!is_null($params)) { $params = $this->trimArray($params); @@ -91,7 +91,7 @@ abstract class ApiParameter * @return mixed * @throws Exception */ - protected function getUlParam(string $param = null, string $ul_field = null, bool $optional = false, $default = 0) + protected function getUlParam(?string $param = null, ?string $ul_field = null, bool $optional = false, $default = 0) { $param_value = (int)$this->getParam($param, $optional, $default); $ul_field_value = $this->getBoolParam($ul_field, true, 0); @@ -116,7 +116,7 @@ abstract class ApiParameter * @return mixed * @throws Exception */ - protected function getParam(string $param = null, bool $optional = false, $default = '') + protected function getParam(?string $param = null, bool $optional = false, $default = '') { // does it exist? if (!isset($this->cmd_params[$param])) { @@ -183,7 +183,7 @@ abstract class ApiParameter * * @return string */ - protected function getBoolParam(string $param = null, bool $optional = false, $default = false) + protected function getBoolParam(?string $param = null, bool $optional = false, $default = false) { $_default = '0'; if ($default) { diff --git a/lib/Froxlor/Api/Commands/Customers.php b/lib/Froxlor/Api/Commands/Customers.php index 428984f7..f2ec06ba 100644 --- a/lib/Froxlor/Api/Commands/Customers.php +++ b/lib/Froxlor/Api/Commands/Customers.php @@ -777,7 +777,7 @@ class Customers extends ApiCommand implements ResourceEntity if ($mysqls != 0) { foreach ($allowed_mysqlserver as $dbserver) { // require privileged access for target db-server - Database::needRoot(true, $dbserver, true); + Database::needRoot(true, $dbserver, false); // get DbManager $dbm = new DbManager($this->logger()); // give permission to the user on every access-host we have diff --git a/lib/Froxlor/Database/Database.php b/lib/Froxlor/Database/Database.php index 14a5fd4b..a8bf0bd4 100644 --- a/lib/Froxlor/Database/Database.php +++ b/lib/Froxlor/Database/Database.php @@ -133,7 +133,7 @@ class Database * if set to false, the error will be logged, but we go on * @throws Exception */ - private static function showerror(Exception $error, bool $showerror = true, bool $json_response = false, PDOStatement $stmt = null) + private static function showerror(Exception $error, bool $showerror = true, bool $json_response = false, ?PDOStatement $stmt = null) { global $userinfo, $theme, $linker; @@ -377,6 +377,14 @@ class Database self::$link = null; } + /** + * get the currently used database-server (relevant for root-connection) + */ + public static function getServer() + { + return self::$dbserver; + } + /** * enable the temporary access to sql-access data * note: if you want root-sqldata you need to diff --git a/lib/Froxlor/Database/DbManager.php b/lib/Froxlor/Database/DbManager.php index 8ef72dab..a3594a10 100644 --- a/lib/Froxlor/Database/DbManager.php +++ b/lib/Froxlor/Database/DbManager.php @@ -176,7 +176,7 @@ class DbManager */ public function createDatabase(string $loginname = null, string $password = null, int $dbserver = 0, int $last_accnumber = 0, string $global_user = "") { - Database::needRoot(true, $dbserver, true); + Database::needRoot(true, $dbserver, false); // check whether we shall create a random username if (strtoupper(Settings::Get('customer.mysqlprefix')) == 'RANDOM') { @@ -211,7 +211,7 @@ class DbManager } $this->getManager()->flushPrivileges(); - Database::needRoot(false); + Database::needRoot(); $this->log->logAction(FroxlorLogger::USR_ACTION, LOG_INFO, "created database '" . $username . "'"); diff --git a/lib/Froxlor/Database/Manager/DbManagerMySQL.php b/lib/Froxlor/Database/Manager/DbManagerMySQL.php index b73992a4..a8876023 100644 --- a/lib/Froxlor/Database/Manager/DbManagerMySQL.php +++ b/lib/Froxlor/Database/Manager/DbManagerMySQL.php @@ -115,12 +115,9 @@ class DbManagerMySQL $grants = "SELECT, INSERT, UPDATE, DELETE, DROP, INDEX, ALTER"; } $stmt = Database::prepare(" - GRANT " . $grants . " ON `" . $username . ($grant_access_prefix ? '%' : '') . "`.* TO :username@:host + GRANT " . $grants . " ON `" . $username . ($grant_access_prefix ? '%' : '') . "`.* TO `" . $username . "`@`" . $access_host . "` "); - Database::pexecute($stmt, [ - "username" => $username, - "host" => $access_host - ]); + Database::pexecute($stmt); if ($grant_access_prefix) { $this->grantCreateToCustomerDbs($username, $access_host); @@ -327,19 +324,22 @@ class DbManagerMySQL */ private function grantCreateToCustomerDbs(string $username, string $access_host) { + // remember what (possible remote) db-server we're on + $currentDbServer = Database::getServer(); + // use "unprivileged" connection + Database::needRoot(); $cus_stmt = Database::prepare("SELECT customerid FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE loginname = :username"); $cust = Database::pexecute_first($cus_stmt, ['username' => $username]); if ($cust) { - $sel_stmt = Database::prepare("SELECT databasename FROM `" . TABLE_PANEL_DATABASES . "` WHERE `customerid` = :cid"); - Database::pexecute($sel_stmt, ['cid' => $cust['customerid']]); + $sel_stmt = Database::prepare("SELECT databasename FROM `" . TABLE_PANEL_DATABASES . "` WHERE `customerid` = :cid AND `dbserver` = :dbserver"); + Database::pexecute($sel_stmt, ['cid' => $cust['customerid'], 'dbserver' => $currentDbServer]); + // reset to root-connection for used dbserver + Database::needRoot(true, $currentDbServer, false); while ($dbdata = $sel_stmt->fetch(\PDO::FETCH_ASSOC)) { $stmt = Database::prepare(" - GRANT ALL ON `" . $dbdata['databasename'] . "`.* TO :username@:host + GRANT ALL ON `" . $dbdata['databasename'] . "`.* TO `" . $username . "`@`" . $access_host . "` "); - Database::pexecute($stmt, [ - "username" => $username, - "host" => $access_host - ]); + Database::pexecute($stmt); } } } @@ -355,12 +355,12 @@ class DbManagerMySQL */ public function grantCreateToDb(string $username, string $database, string $access_host) { - $stmt = Database::prepare(" - GRANT ALL ON `" . $database . "`.* TO :username@:host - "); - Database::pexecute($stmt, [ - "username" => $username, - "host" => $access_host - ]); + // only grant permission if the user exists + if ($this->userExistsOnHost($username, $access_host)) { + $stmt = Database::prepare(" + GRANT ALL ON `" . $database . "`.* TO `" . $username . "`@`" . $access_host . "` + "); + Database::pexecute($stmt); + } } } diff --git a/lib/Froxlor/FroxlorLogger.php b/lib/Froxlor/FroxlorLogger.php index 5c37bb75..7c29bfea 100644 --- a/lib/Froxlor/FroxlorLogger.php +++ b/lib/Froxlor/FroxlorLogger.php @@ -164,7 +164,7 @@ class FroxlorLogger * @param int $type * @param ?string $text */ - public function logAction($action = FroxlorLogger::USR_ACTION, int $type = LOG_NOTICE, string $text = null) + public function logAction($action = FroxlorLogger::USR_ACTION, int $type = LOG_NOTICE, ?string $text = null) { // not logging normal stuff if not set to "paranoid" logging if (!self::$crondebug_flag && Settings::Get('logger.severity') == '1' && $type > LOG_NOTICE) { diff --git a/lib/Froxlor/PhpHelper.php b/lib/Froxlor/PhpHelper.php index 7470e90d..5bcc157b 100644 --- a/lib/Froxlor/PhpHelper.php +++ b/lib/Froxlor/PhpHelper.php @@ -199,7 +199,7 @@ class PhpHelper * @param string|null $nameserver set additional resolver nameserver to use (e.g. 1.1.1.1) * @return bool|array */ - public static function gethostbynamel6(string $host, bool $try_a = true, string $nameserver = null) + public static function gethostbynamel6(string $host, bool $try_a = true, ?string $nameserver = null) { $ips = []; @@ -442,7 +442,7 @@ class PhpHelper * @param bool $asReturn * @return string */ - public static function parseArrayToPhpFile(array $array, string $comment = null, bool $asReturn = false): string + public static function parseArrayToPhpFile(array $array, ?string $comment = null, bool $asReturn = false): string { $str = sprintf("data['sql_search'])) { $this->data['sql_search'] = []; diff --git a/lib/Froxlor/Validate/Validate.php b/lib/Froxlor/Validate/Validate.php index 349e6d56..4a03109e 100644 --- a/lib/Froxlor/Validate/Validate.php +++ b/lib/Froxlor/Validate/Validate.php @@ -28,7 +28,6 @@ namespace Froxlor\Validate; use Exception; use Froxlor\Database\Database; use Froxlor\FroxlorLogger; -use Froxlor\Idna\IdnaWrapper; use Froxlor\System\IPTools; use Froxlor\UI\Response; @@ -63,10 +62,11 @@ class Validate string $str, string $fieldname, string $pattern = '', - $lng = '', - $emptydefault = [], - bool $throw_exception = false - ) { + $lng = '', + $emptydefault = [], + bool $throw_exception = false + ) + { if (!is_array($emptydefault)) { $emptydefault_array = [ $emptydefault @@ -122,14 +122,15 @@ class Validate */ public static function validate_ip2( string $ip, - bool $return_bool = false, + bool $return_bool = false, string $lng = 'invalidip', - bool $allow_localhost = false, - bool $allow_priv = false, - bool $allow_cidr = false, - bool $cidr_as_netmask = false, - bool $throw_exception = false - ) { + bool $allow_localhost = false, + bool $allow_priv = false, + bool $allow_cidr = false, + bool $cidr_as_netmask = false, + bool $throw_exception = false + ) + { $cidr = ""; if ($allow_cidr) { $org_ip = $ip; @@ -200,20 +201,34 @@ class Validate $url = 'http://' . $url; } - // needs converting - try { - $idna_convert = new IdnaWrapper(); - $url = $idna_convert->encode($url); - } catch (Exception $e) { + // Parse parts + $parts = parse_url($url); + if ($parts === false || !isset($parts['scheme'], $parts['host'])) { return false; } - if ($allow_private_ip) { - $pattern = '%^(?:(?:https?):\/\/)(?:\S+(?::\S*)?@)?(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$%iuS'; - } else { - $pattern = '%^(?:(?:https?):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,})))(?::\d{2,5})?(?:/[^\s]*)?$%iuS'; + // Check allowed schemes + if (!in_array(strtolower($parts['scheme']), ['http', 'https'], true)) { + return false; } - if (preg_match($pattern, $url)) { + + // Check if host is valid domain or valid IP (v4 or v6) + $host = $parts['host']; + if (substr($host, 0, 1) == '[' && substr($host, -1) == ']') { + $host = substr($host, 1, -1); + } + + $opts = FILTER_FLAG_IPV4 | FILTER_FLAG_NO_RES_RANGE | FILTER_FLAG_NO_PRIV_RANGE; + $opts6 = FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE | FILTER_FLAG_NO_PRIV_RANGE; + if ($allow_private_ip) { + $opts = FILTER_FLAG_IPV4 | FILTER_FLAG_NO_RES_RANGE; + $opts6 = FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE; + } + if (filter_var($host, FILTER_VALIDATE_IP, $opts)) { + return true; + } elseif (substr($parts['host'], 0, 1) == '[' && substr($parts['host'], -1) == ']' && filter_var($host, FILTER_VALIDATE_IP, $opts6)) { + return true; + } elseif (!preg_match('/^([0-9]{1,3}\.)+[0-9]{1,3}$/', $host) && self::validateDomain($host) !== false) { return true; } @@ -342,7 +357,8 @@ class Validate * @return bool * @throws Exception */ - public static function validateBase64Image(string $base64string) { + public static function validateBase64Image(string $base64string) + { if (!extension_loaded('gd')) { Response::standardError('phpgdextensionnotavailable', null, true); diff --git a/lib/configfiles/bookworm.xml b/lib/configfiles/bookworm.xml index f60744fb..528446e9 100644 --- a/lib/configfiles/bookworm.xml +++ b/lib/configfiles/bookworm.xml @@ -2599,6 +2599,7 @@ try_fallback = true; allow_username_mismatch = true; path = "/var/lib/rspamd/dkim/$domain.$selector.key"; selector_map = "/etc/rspamd/dkim_selectors.map"; +use_esld = false; ]]> diff --git a/lib/configfiles/bullseye.xml b/lib/configfiles/bullseye.xml index 062def86..adfeb15f 100644 --- a/lib/configfiles/bullseye.xml +++ b/lib/configfiles/bullseye.xml @@ -4168,6 +4168,7 @@ try_fallback = true; allow_username_mismatch = true; path = "/var/lib/rspamd/dkim/$domain.$selector.key"; selector_map = "/etc/rspamd/dkim_selectors.map"; +use_esld = false; ]]> diff --git a/lib/configfiles/focal.xml b/lib/configfiles/focal.xml index 7054a2f3..45c98ac4 100644 --- a/lib/configfiles/focal.xml +++ b/lib/configfiles/focal.xml @@ -3391,6 +3391,7 @@ try_fallback = true; allow_username_mismatch = true; path = "/var/lib/rspamd/dkim/$domain.$selector.key"; selector_map = "/etc/rspamd/dkim_selectors.map"; +use_esld = false; ]]> diff --git a/lib/configfiles/jammy.xml b/lib/configfiles/jammy.xml index ace26c6b..4c4d5761 100644 --- a/lib/configfiles/jammy.xml +++ b/lib/configfiles/jammy.xml @@ -3381,6 +3381,7 @@ try_fallback = true; allow_username_mismatch = true; path = "/var/lib/rspamd/dkim/$domain.$selector.key"; selector_map = "/etc/rspamd/dkim_selectors.map"; +use_esld = false; ]]> diff --git a/lib/configfiles/noble.xml b/lib/configfiles/noble.xml index 9a727992..0a021258 100644 --- a/lib/configfiles/noble.xml +++ b/lib/configfiles/noble.xml @@ -2054,6 +2054,7 @@ try_fallback = true; allow_username_mismatch = true; path = "/var/lib/rspamd/dkim/$domain.$selector.key"; selector_map = "/etc/rspamd/dkim_selectors.map"; +use_esld = false; ]]> diff --git a/package-lock.json b/package-lock.json index c9e0f8f7..09a46b14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "@fortawesome/fontawesome-free": "^6.7.2", "@popperjs/core": "^2.11.8", "@vitejs/plugin-vue": "^5.2.1", - "axios": "^1.8.1", + "axios": "^1.8.2", "bootstrap": "^5.3.3", "chart.js": "^4.4.8", "jquery": "^3.7.1", @@ -19,7 +19,7 @@ "postcss": "^8.5.3", "resolve-url-loader": "^5.0.0", "sass": "^1.85.1", - "vite": "^6.2.0", + "vite": "^6.2.6", "vue": "^3.5.13" }, "engines": { @@ -1264,9 +1264,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz", - "integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", + "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -2107,9 +2107,9 @@ } }, "node_modules/vite": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz", - "integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", + "version": "6.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz", + "integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 09d71026..e602fce4 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "@fortawesome/fontawesome-free": "^6.7.2", "@popperjs/core": "^2.11.8", "@vitejs/plugin-vue": "^5.2.1", - "axios": "^1.8.1", + "axios": "^1.8.2", "bootstrap": "^5.3.3", "chart.js": "^4.4.8", "jquery": "^3.7.1", @@ -19,7 +19,7 @@ "postcss": "^8.5.3", "resolve-url-loader": "^5.0.0", "sass": "^1.85.1", - "vite": "^6.2.0", + "vite": "^6.2.6", "vue": "^3.5.13" }, "engines": { diff --git a/templates/Froxlor/login/enter2fa.html.twig b/templates/Froxlor/login/enter2fa.html.twig index 5f2ad725..7708c5e3 100644 --- a/templates/Froxlor/login/enter2fa.html.twig +++ b/templates/Froxlor/login/enter2fa.html.twig @@ -19,7 +19,7 @@
- +
{% if remember_me %} diff --git a/tests/Froxlor/ValidateTest.php b/tests/Froxlor/ValidateTest.php index f29dfb42..b33360a8 100644 --- a/tests/Froxlor/ValidateTest.php +++ b/tests/Froxlor/ValidateTest.php @@ -142,6 +142,12 @@ class ValidateTest extends TestCase $this->assertFalse($result); $result = Validate::validateUrl("172.16.0.1:8080", true); $this->assertTrue($result); + $result = Validate::validateUrl("https://xn--frxlr-kuac.de/", true); + $this->assertTrue($result); + $result = Validate::validateUrl("https://2a10:ec2::193:107:51:5/test"); + $this->assertFalse($result); + $result = Validate::validateUrl("https://[2a10:ec2::193:107:51:5]"); + $this->assertTrue($result); } public function testValidateDomain()