Compare commits

...

35 Commits

Author SHA1 Message Date
Michael Kaufmann
6cd061d74c set version to 0.10.4 for upcoming maintenance release; minor code formatting + adjustments
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2019-11-01 11:16:13 +01:00
Michael Kaufmann
53b7420dc9 fix stripping of escape-sequences in api-endpoint; fixes #746
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2019-11-01 07:39:28 +01:00
Michael Kaufmann
aa85c648a3 check for renewal of certificates not only if there's a task to regenerate vhosts but everytime the letsencrypt cronjob runs (which is basically obsolete due to the integration into the tasks cron but perfect for checking renewal dates
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2019-10-31 21:37:54 +01:00
Michael Kaufmann
35e228ff09 Merge pull request #745 from pquerner/unittests/564
Add UnitTests for #679
2019-10-30 13:01:02 +01:00
Pascal
62236da496 changed method name 2019-10-30 12:50:16 +01:00
Pascal
e1cc896b6c add unit tests for Validate::is_ipv6 2019-10-30 12:39:56 +01:00
Pascal
36595baa65 Merge remote-tracking branch 'Froxlor/master' 2019-10-30 12:14:39 +01:00
Michael Kaufmann
ec3fd1d105 Create SECURITY.md 2019-10-30 11:00:08 +01:00
Michael Kaufmann
e39dcfbfe2 Update FUNDING.yml 2019-10-30 10:50:20 +01:00
Michael Kaufmann
ef6254b307 Merge pull request #679 from pquerner/#564
Allow CIDR and Netmask in mysql_host_access; fixes #564
2019-10-30 10:40:26 +01:00
Michael Kaufmann
44bf211ab5 Merge pull request #743 from kionez/fix_split_path_info
Correct fastcgi_split_path_info; fixes #744
2019-10-29 16:09:44 +01:00
kionez
b0e920104f Fix fastcgi_split_path_info as https://www.nginx.com/resources/wiki/start/topics/examples/phpfcgi/ 2019-10-29 16:00:14 +01:00
kionez
299e201142 Fix fastcgi_split_path_info 2019-10-29 15:47:28 +01:00
Michael Kaufmann
46982ad2dc validate that a customer gets the default ftp account created even if the admin/reseller has no more resource for ftp accounts; fixes #741
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2019-10-29 07:52:00 +01:00
Michael Kaufmann
c0e07fd659 fix undefined variable in hosting-plans frontend, fixes #742
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2019-10-28 20:06:14 +01:00
Pascal
5c11eecbd7 remove code for checking ipv6 mapped ipv4 notation 2019-10-28 17:27:39 +01:00
Pascal
9689afc759 change method signature of \Froxlor\Validate\Validate::validate_ip2 2019-10-28 16:58:34 +01:00
Pascal
d76f4108e5 dont need $result if we're expecting an exception 2019-10-28 16:40:22 +01:00
Pascal
9c4d619840 remove inner if statement
check ipv6 when cidr>netmask flag is set
2019-10-28 16:32:52 +01:00
Pascal
7774e7606d dont check notated ips again 2019-10-28 16:29:53 +01:00
Pascal
2ed0cad27b #564:
cidr notation can only be 1 through 32
2019-10-28 16:27:54 +01:00
Pascal
686c2ae534 fix comparison 2019-10-28 16:00:43 +01:00
Pascal
faf3abe800 introduce new parameter to allow automatic convert cidr notation to netmask notation 2019-10-28 15:33:26 +01:00
Pascal
220b493a1b better readability 2019-10-28 14:16:27 +01:00
Pascal
e8d67f9711 check if ipv6 first 2019-10-28 14:07:31 +01:00
Pascal
83e932b068 switch join with implode 2019-10-28 13:26:32 +01:00
Pascal
84d1be538e block ipv6 addresses in cidr notation (mysql can't handle it) 2019-10-28 13:25:34 +01:00
Pascal
c97cdb1c0e make it more readable 2019-10-28 13:15:48 +01:00
Pascal
ffefe85fb4 Merge branch 'master' into #564 2019-10-28 12:18:55 +01:00
Pascal
27341ca490 Merge remote-tracking branch 'Froxlor/master' 2019-10-28 12:17:51 +01:00
Michael Kaufmann
822bb2bd4d fixed deletion of default-ftp-user possible via API (not through the interface though); refs #741
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2019-10-27 16:02:32 +01:00
Pascal
836b6f2fdb Merge remote-tracking branch 'upstream/master' 2019-05-10 02:54:33 +02:00
Pascal
f297058461 #564
fix wording
add validation for cidr syntax
add automatic transform of cidr to netmask for mysql
2019-05-04 00:39:12 +02:00
Pascal
0f4d8d76ae #564
fix wording
2019-05-03 23:31:31 +02:00
Pascal
12884c91a6 #564
fix #564 by allowing CIDR in mysql host validation. 
fix english and german field description accordingly
2019-05-03 22:32:57 +02:00
22 changed files with 335 additions and 53 deletions

1
.github/FUNDING.yml vendored
View File

@@ -1,3 +1,4 @@
# These are supported funding model platforms # These are supported funding model platforms
github: d00p
custom: ['https://paypal.me/Froxlor'] custom: ['https://paypal.me/Froxlor']

14
SECURITY.md Normal file
View File

@@ -0,0 +1,14 @@
# Security Policy
## Supported Versions
Our main and active version is currently 0.10.x. It will receive maintenance and security updates periodically. The older version 0.9.x will not receive any kind of updates. Please update to [0.10.x](https://github.com/Froxlor/Froxlor/wiki/Updating-Froxlor)
| Version | Supported |
| ------- | ------------------ |
| 0.10.x | :white_check_mark: |
| 0.9.x | :x: |
## Reporting a Vulnerability
If you think you have found a vulnerability in froxlor, please send an email to [team@froxlor.org](mailto:team@froxlor.org) with as many information as possible. Also, please give us appropriate time to fix the issue and build update-packages before publishing anything into the wild.

View File

@@ -289,6 +289,7 @@ if ($page == '' || $page == 'overview') {
$result['customernumber'] = null; $result['customernumber'] = null;
$result['custom_notes'] = null; $result['custom_notes'] = null;
$result['custom_notes_show'] = null; $result['custom_notes_show'] = null;
$result['api_allowed'] = null;
$hosting_plans = null; $hosting_plans = null;
$admin_select_cnt = null; $admin_select_cnt = null;
$admin_select = null; $admin_select = null;

View File

@@ -23,7 +23,7 @@ if (empty($request)) {
} }
// decode json request // decode json request
$decoded_request = json_decode(stripslashes($request), true); $decoded_request = json_decode($request, true);
// is it valid? // is it valid?
if (is_null($decoded_request)) { if (is_null($decoded_request)) {
@@ -32,6 +32,7 @@ if (is_null($decoded_request)) {
// validate content // validate content
try { try {
$decoded_request = stripcslashes_deep($decoded_request);
$request = \Froxlor\Api\FroxlorRPC::validateRequest($decoded_request); $request = \Froxlor\Api\FroxlorRPC::validateRequest($decoded_request);
// now actually do it // now actually do it
$cls = "\\Froxlor\\Api\\Commands\\" . $request['command']['class']; $cls = "\\Froxlor\\Api\\Commands\\" . $request['command']['class'];
@@ -72,3 +73,8 @@ function json_response($status, $status_message = '', $data = null)
echo $json_response; echo $json_response;
exit(); exit();
} }
function stripcslashes_deep($value)
{
return is_array($value) ? array_map('stripcslashes_deep', $value) : stripcslashes($value);
}

View File

@@ -695,7 +695,7 @@ opcache.interned_strings_buffer'),
('panel', 'password_special_char', '!?<>§$%+#=@'), ('panel', 'password_special_char', '!?<>§$%+#=@'),
('panel', 'customer_hide_options', ''), ('panel', 'customer_hide_options', ''),
('panel', 'is_configured', '0'), ('panel', 'is_configured', '0'),
('panel', 'version', '0.10.3'), ('panel', 'version', '0.10.4'),
('panel', 'db_version', '201910200'); ('panel', 'db_version', '201910200');

View File

@@ -406,9 +406,9 @@ if (\Froxlor\Froxlor::isDatabaseVersion('201910110')) {
// select all domains with an ssl IP connected and specialsettings content to include these in the ssl-vhost // select all domains with an ssl IP connected and specialsettings content to include these in the ssl-vhost
// to maintain former behavior // to maintain former behavior
$sel_stmt = Database::prepare(" $sel_stmt = Database::prepare("
SELECT d.id FROM `". TABLE_PANEL_DOMAINS . "` d SELECT d.id FROM `" . TABLE_PANEL_DOMAINS . "` d
LEFT JOIN `". TABLE_DOMAINTOIP . "` d2i ON d2i.id_domain = d.id LEFT JOIN `" . TABLE_DOMAINTOIP . "` d2i ON d2i.id_domain = d.id
LEFT JOIN `". TABLE_PANEL_IPSANDPORTS."` i ON i.id = d2i.id_ipandports LEFT JOIN `" . TABLE_PANEL_IPSANDPORTS . "` i ON i.id = d2i.id_ipandports
WHERE d.specialsettings <> '' AND i.ssl = '1' WHERE d.specialsettings <> '' AND i.ssl = '1'
"); ");
Database::pexecute($sel_stmt); Database::pexecute($sel_stmt);
@@ -444,6 +444,11 @@ if (\Froxlor\Froxlor::isDatabaseVersion('201910120')) {
} }
if (\Froxlor\Froxlor::isFroxlorVersion('0.10.2')) { if (\Froxlor\Froxlor::isFroxlorVersion('0.10.2')) {
showUpdateStep("Updating from 0.10.2 to 0.10.3", false); showUpdateStep("Updating from 0.10.2 to 0.10.3", false);
\Froxlor\Froxlor::updateToVersion('0.10.3'); \Froxlor\Froxlor::updateToVersion('0.10.3');
}
if (\Froxlor\Froxlor::isFroxlorVersion('0.10.3')) {
showUpdateStep("Updating from 0.10.3 to 0.10.4", false);
\Froxlor\Froxlor::updateToVersion('0.10.4');
} }

View File

@@ -56,7 +56,9 @@ class Ftps extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\ResourceEntit
throw new \Exception("You cannot access this resource", 405); throw new \Exception("You cannot access this resource", 405);
} }
if ($this->getUserDetail('ftps_used') < $this->getUserDetail('ftps') || $this->getUserDetail('ftps') == '-1') { $is_defaultuser = $this->getBoolParam('is_defaultuser', true, 0);
if (($this->getUserDetail('ftps_used') < $this->getUserDetail('ftps') || $this->getUserDetail('ftps') == '-1') || $this->isAdmin() && $is_defaultuser == 1) {
// required paramters // required paramters
$path = $this->getParam('path'); $path = $this->getParam('path');
@@ -71,7 +73,6 @@ class Ftps extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\ResourceEntit
$ftpdomain = $this->getParam('ftp_domain', true, ''); $ftpdomain = $this->getParam('ftp_domain', true, '');
$additional_members = $this->getParam('additional_members', true, array()); $additional_members = $this->getParam('additional_members', true, array());
$is_defaultuser = $this->getBoolParam('is_defaultuser', true, 0);
// validation // validation
$password = \Froxlor\Validate\Validate::validate($password, 'password', '', '', array(), true); $password = \Froxlor\Validate\Validate::validate($password, 'password', '', '', array(), true);
@@ -105,7 +106,7 @@ class Ftps extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\ResourceEntit
$sendinfomail = 0; $sendinfomail = 0;
} }
if (Settings::Get('customer.ftpatdomain') == '1' && !$is_defaultuser) { if (Settings::Get('customer.ftpatdomain') == '1' && ! $is_defaultuser) {
if ($ftpusername == '') { if ($ftpusername == '') {
\Froxlor\UI\Response::standard_error(array( \Froxlor\UI\Response::standard_error(array(
'stringisempty', 'stringisempty',
@@ -541,6 +542,9 @@ class Ftps extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\ResourceEntit
"username" => $customer_data['loginname'] "username" => $customer_data['loginname']
); );
Database::pexecute($stmt, $params, true, true); Database::pexecute($stmt, $params, true, true);
} else {
// do not allow removing default ftp-account
\Froxlor\UI\Response::standard_error('ftp_cantdeletemainaccount', '', true);
} }
// remove all quotatallies // remove all quotatallies

View File

@@ -135,7 +135,7 @@ class IpsAndPorts extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\Resour
{ {
if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { 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( $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', 'stringisempty',
'myport' 'myport'
@@ -332,7 +332,7 @@ class IpsAndPorts extends \Froxlor\Api\ApiCommand implements \Froxlor\Api\Resour
'id' => $id '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( $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', 'stringisempty',
'myport' 'myport'

View File

@@ -45,19 +45,8 @@ class AcmeSh extends \Froxlor\Cron\FroxlorCron
public static $no_inserttask = false; public static $no_inserttask = false;
public static function run($internal = false) private static function needRenew()
{ {
if (! defined('CRON_IS_FORCED') && ! defined('CRON_DEBUG_FLAG') && $internal == false) {
// FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "Let's Encrypt cronjob is combined with regeneration of webserver configuration files.\nFor debugging purposes you can use the --debug switch and/or the --force switch to run the cron manually.");
return 0;
}
self::checkInstall();
self::$apiserver = 'https://acme-v0' . \Froxlor\Settings::Get('system.leapiversion') . '.api.letsencrypt.org/directory';
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Requesting/renewing Let's Encrypt certificates");
$certificates_stmt = Database::query(" $certificates_stmt = Database::query("
SELECT SELECT
domssl.`id`, domssl.`id`,
@@ -94,6 +83,46 @@ class AcmeSh extends \Froxlor\Cron\FroxlorCron
OR domssl.`expirationdate` IS NULL OR domssl.`expirationdate` IS NULL
) )
"); ");
$customer_ssl = $certificates_stmt->fetchAll(\PDO::FETCH_ASSOC);
$froxlor_ssl = array();
if (Settings::Get('system.le_froxlor_enabled') == '1') {
$froxlor_ssl_settings_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "`
WHERE `domainid` = '0' AND
(`expirationdate` < DATE_ADD(NOW(), INTERVAL 30 DAY) OR `expirationdate` IS NULL)
");
$froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt);
}
if (count($customer_ssl) > 0 || count($froxlor_ssl) > 0) {
return array(
'customer_ssl' => $customer_ssl,
'froxlor_ssl' => $froxlor_ssl
);
}
return false;
}
public static function run($internal = false)
{
if (! defined('CRON_IS_FORCED') && ! defined('CRON_DEBUG_FLAG') && $internal == false) {
// Let's Encrypt cronjob is combined with regeneration of webserver configuration files.
// For debugging purposes you can use the --debug switch and the --force switch to run the cron manually.
// check whether we MIGHT need to run although there is no task to regenerate config-files
$needRenew = self::needRenew();
if ($needRenew) {
// insert task to generate certificates and vhost-configs
\Froxlor\System\Cronjob::inserttask(1);
}
return 0;
}
self::checkInstall();
self::$apiserver = 'https://acme-v0' . \Froxlor\Settings::Get('system.leapiversion') . '.api.letsencrypt.org/directory';
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Requesting/renewing Let's Encrypt certificates");
$aliasdomains_stmt = Database::prepare(" $aliasdomains_stmt = Database::prepare("
SELECT SELECT
@@ -127,6 +156,8 @@ class AcmeSh extends \Froxlor\Cron\FroxlorCron
// flag for re-generation of vhost files // flag for re-generation of vhost files
$changedetected = 0; $changedetected = 0;
$needRenew = self::needRenew();
// first - generate LE for system-vhost if enabled // first - generate LE for system-vhost if enabled
if (Settings::Get('system.le_froxlor_enabled') == '1') { if (Settings::Get('system.le_froxlor_enabled') == '1') {
@@ -147,12 +178,7 @@ class AcmeSh extends \Froxlor\Cron\FroxlorCron
'id' => null 'id' => null
); );
$froxlor_ssl_settings_stmt = Database::prepare(" $froxlor_ssl = $needRenew['froxlor_ssl'];
SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "`
WHERE `domainid` = '0' AND
(`expirationdate` < DATE_ADD(NOW(), INTERVAL 30 DAY) OR `expirationdate` IS NULL)
");
$froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt);
$cert_mode = 'issue'; $cert_mode = 'issue';
if ($froxlor_ssl) { if ($froxlor_ssl) {
@@ -210,7 +236,7 @@ class AcmeSh extends \Froxlor\Cron\FroxlorCron
} }
// customer domains // customer domains
$certrows = $certificates_stmt->fetchAll(\PDO::FETCH_ASSOC); $certrows = $needRenew['customer_ssl'];
$cert_mode = 'issue'; $cert_mode = 'issue';
foreach ($certrows as $certrow) { foreach ($certrows as $certrow) {
@@ -390,6 +416,6 @@ class AcmeSh extends \Froxlor\Cron\FroxlorCron
$acmesh_result = \Froxlor\FileDir::safe_exec(self::$acmesh . " --upgrade"); $acmesh_result = \Froxlor\FileDir::safe_exec(self::$acmesh . " --upgrade");
// check for activated cron (which is installed automatically) but we don't need it // check for activated cron (which is installed automatically) but we don't need it
$acmesh_result2 = \Froxlor\FileDir::safe_exec(self::$acmesh . " --uninstall-cronjob"); $acmesh_result2 = \Froxlor\FileDir::safe_exec(self::$acmesh . " --uninstall-cronjob");
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Checking for LetsEncrypt client upgrades before renewing certificates:\n" . implode("\n", $acmesh_result)."\n".implode("\n", $acmesh_result2)); FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Checking for LetsEncrypt client upgrades before renewing certificates:\n" . implode("\n", $acmesh_result) . "\n" . implode("\n", $acmesh_result2));
} }
} }

View File

@@ -285,7 +285,7 @@ class Nginx extends HttpConfigBase
if (! $is_redirect) { if (! $is_redirect) {
$this->nginx_data[$vhost_filename] .= "\tlocation ~ \.php {\n"; $this->nginx_data[$vhost_filename] .= "\tlocation ~ \.php {\n";
$this->nginx_data[$vhost_filename] .= "\t\tfastcgi_split_path_info ^(.+\.php)(/.+)\$;\n"; $this->nginx_data[$vhost_filename] .= "\t\tfastcgi_split_path_info ^(.+?\.php)(/.*)$;\n";
$this->nginx_data[$vhost_filename] .= "\t\tinclude " . Settings::Get('nginx.fastcgiparams') . ";\n"; $this->nginx_data[$vhost_filename] .= "\t\tinclude " . Settings::Get('nginx.fastcgiparams') . ";\n";
$this->nginx_data[$vhost_filename] .= "\t\tfastcgi_param SCRIPT_FILENAME \$request_filename;\n"; $this->nginx_data[$vhost_filename] .= "\t\tfastcgi_param SCRIPT_FILENAME \$request_filename;\n";
$this->nginx_data[$vhost_filename] .= "\t\tfastcgi_param PATH_INFO \$fastcgi_path_info;\n"; $this->nginx_data[$vhost_filename] .= "\t\tfastcgi_param PATH_INFO \$fastcgi_path_info;\n";
@@ -955,7 +955,7 @@ class Nginx extends HttpConfigBase
$phpopts .= "\t" . '}' . "\n\n"; $phpopts .= "\t" . '}' . "\n\n";
$phpopts .= "\tlocation @php {\n"; $phpopts .= "\tlocation @php {\n";
$phpopts .= "\t\tfastcgi_split_path_info ^(.+\.php)(/.+)\$;\n"; $phpopts .= "\t\tfastcgi_split_path_info ^(.+?\.php)(/.*)$;\n";
$phpopts .= "\t\tinclude " . Settings::Get('nginx.fastcgiparams') . ";\n"; $phpopts .= "\t\tinclude " . Settings::Get('nginx.fastcgiparams') . ";\n";
$phpopts .= "\t\tfastcgi_param SCRIPT_FILENAME \$request_filename;\n"; $phpopts .= "\t\tfastcgi_param SCRIPT_FILENAME \$request_filename;\n";
$phpopts .= "\t\tfastcgi_param PATH_INFO \$fastcgi_path_info;\n"; $phpopts .= "\t\tfastcgi_param PATH_INFO \$fastcgi_path_info;\n";

View File

@@ -37,7 +37,7 @@ class NginxFcgi extends Nginx
$php_options_text .= "\t" . 'location @php {' . "\n"; $php_options_text .= "\t" . 'location @php {' . "\n";
$php_options_text .= "\t\t" . 'try_files $1 =404;' . "\n\n"; $php_options_text .= "\t\t" . 'try_files $1 =404;' . "\n\n";
$php_options_text .= "\t\t" . 'include ' . Settings::Get('nginx.fastcgiparams') . ";\n"; $php_options_text .= "\t\t" . 'include ' . Settings::Get('nginx.fastcgiparams') . ";\n";
$php_options_text .= "\t\t" . 'fastcgi_split_path_info ^(.+\.php)(/.+)\$;' . "\n"; $php_options_text .= "\t\t" . 'fastcgi_split_path_info ^(.+?\.php)(/.*)$;' . "\n";
$php_options_text .= "\t\t" . 'fastcgi_param SCRIPT_FILENAME $request_filename;' . "\n"; $php_options_text .= "\t\t" . 'fastcgi_param SCRIPT_FILENAME $request_filename;' . "\n";
$php_options_text .= "\t\t" . 'fastcgi_param PATH_INFO $2;' . "\n"; $php_options_text .= "\t\t" . 'fastcgi_param PATH_INFO $2;' . "\n";
if ($domain['ssl'] == '1' && $ssl_vhost) { if ($domain['ssl'] == '1' && $ssl_vhost) {

View File

@@ -7,7 +7,7 @@ final class Froxlor
{ {
// Main version variable // Main version variable
const VERSION = '0.10.3'; const VERSION = '0.10.4';
// Database version (YYYYMMDDC where C is a daily counter) // Database version (YYYYMMDDC where C is a daily counter)
const DBVERSION = '201910200'; const DBVERSION = '201910200';

View File

@@ -252,6 +252,28 @@ class Store
public static function storeSettingMysqlAccessHost($fieldname, $fielddata, $newfieldvalue) 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); $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') { if ($returnvalue !== false && is_array($fielddata) && isset($fielddata['settinggroup']) && $fielddata['settinggroup'] == 'system' && isset($fielddata['varname']) && $fielddata['varname'] == 'mysql_access_host') {

View File

@@ -77,8 +77,7 @@ class Check
$mysql_access_host_array = array_map('trim', explode(',', $newfieldvalue)); $mysql_access_host_array = array_map('trim', explode(',', $newfieldvalue));
foreach ($mysql_access_host_array as $host_entry) { foreach ($mysql_access_host_array as $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 != '%') {
if (Validate::validate_ip2($host_entry, true, 'invalidip', true, true) == false && Validate::validateDomain($host_entry) == false && Validate::validateLocalHostname($host_entry) == false && $host_entry != '%') {
return array( return array(
self::FORMFIELDS_PLAUSIBILITY_CHECK_ERROR, self::FORMFIELDS_PLAUSIBILITY_CHECK_ERROR,
'invalidmysqlhost', 'invalidmysqlhost',

View File

@@ -62,6 +62,38 @@ class Validate
\Froxlor\UI\Response::standard_error($lng, $fieldname, $throw_exception); \Froxlor\UI\Response::standard_error($lng, $fieldname, $throw_exception);
} }
/**
* 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 string $address
*
* @return string|bool ip address on success, false on failure
*/
public static function is_ipv6($address)
{
return filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
}
/** /**
* Checks whether it is a valid ip * Checks whether it is a valid ip
* *
@@ -77,17 +109,35 @@ class Validate
* whether to allow private network addresses * whether to allow private network addresses
* @param bool $allow_cidr * @param bool $allow_cidr
* whether to allow CIDR values e.g. 10.10.10.10/16 * 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 * @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) 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 = ""; $cidr = "";
if ($allow_cidr) { if ($allow_cidr) {
$org_ip = $ip; $org_ip = $ip;
$ip_cidr = explode("/", $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
if ($return_bool) {
return false;
} else {
\Froxlor\UI\Response::standard_error($lng, $ip, $throw_exception);
}
}
$ip = $ip_cidr[0]; $ip = $ip_cidr[0];
if ($cidr_as_netmask && strlen($ip_cidr[1]) <= 2) {
$ip_cidr[1] = self::cidr2NetmaskAddr($org_ip);
}
$cidr = "/" . $ip_cidr[1]; $cidr = "/" . $ip_cidr[1];
} else { } else {
$ip = $org_ip; $ip = $org_ip;

View File

@@ -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['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']['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 // ADDED IN 1.2.18-svn1

View File

@@ -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['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']['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 // ADDED IN 1.2.18-svn1

View File

@@ -189,7 +189,6 @@ class CertificatesTest extends TestCase
)); ));
// export // export
openssl_csr_export($csr, $csrout);
openssl_x509_export($sscert, $certout); openssl_x509_export($sscert, $certout);
openssl_pkey_export($privkey, $pkeyout, null); openssl_pkey_export($privkey, $pkeyout, null);

View File

@@ -6,6 +6,7 @@ use Froxlor\Database\Database;
use Froxlor\Api\Commands\Admins; use Froxlor\Api\Commands\Admins;
use Froxlor\Api\Commands\Customers; use Froxlor\Api\Commands\Customers;
use Froxlor\Api\Commands\SubDomains; use Froxlor\Api\Commands\SubDomains;
use Froxlor\Api\Commands\Ftps;
/** /**
* *
@@ -61,7 +62,9 @@ class CustomersTest extends TestCase
$this->assertEquals('secret', $result['custom_notes']); $this->assertEquals('secret', $result['custom_notes']);
// validate that the std-subdomain has been added // validate that the std-subdomain has been added
$json_result = SubDomains::getLocal($admin_userdata, array('id' => $result['standardsubdomain']))->get(); $json_result = SubDomains::getLocal($admin_userdata, array(
'id' => $result['standardsubdomain']
))->get();
$result = json_decode($json_result, true)['data']; $result = json_decode($json_result, true)['data'];
$this->assertEquals('test1.dev.froxlor.org', $result['domain']); $this->assertEquals('test1.dev.froxlor.org', $result['domain']);
} }
@@ -555,4 +558,49 @@ class CustomersTest extends TestCase
$this->expectExceptionMessage('Loginname contains too many characters. Only ' . (\Froxlor\Database\Database::getSqlUsernameLength() - strlen(Settings::Get('customer.mysqlprefix'))) . ' characters are allowed.'); $this->expectExceptionMessage('Loginname contains too many characters. Only ' . (\Froxlor\Database\Database::getSqlUsernameLength() - strlen(Settings::Get('customer.mysqlprefix'))) . ' characters are allowed.');
Customers::getLocal($admin_userdata, $data)->add(); Customers::getLocal($admin_userdata, $data)->add();
} }
/**
*
* @depends testAdminCustomersAddAutoLoginname
*/
public function testResellerCustomersAddNoFtpValidateDefaultUserExists()
{
global $admin_userdata;
// get reseller
$json_result = Admins::getLocal($admin_userdata, array(
'loginname' => 'reseller'
))->get();
$reseller_userdata = json_decode($json_result, true)['data'];
$reseller_userdata['adminsession'] = 1;
// set available ftp resources to 0 to validate that when the customer
// is added the default ftp user for the customer is created too regardless of
// available resource of the reseller/admin
$reseller_userdata['ftps'] = 0;
// add new customer
$data = [
'new_loginname' => 'testftpx',
'email' => 'testftp@froxlor.org',
'firstname' => 'Test',
'name' => 'Ftpman',
'customernumber' => 1339,
'new_customer_password' => 'h0lYmo1y'
];
Customers::getLocal($reseller_userdata, $data)->add();
// get FTP user
$json_result = Ftps::getLocal($reseller_userdata, [
'username' => 'testftpx'
])->get();
$ftp_data = json_decode($json_result, true)['data'];
$this->assertEquals("testftpx", $ftp_data['username']);
// now get rid of the customer again
$json_result = Customers::getLocal($reseller_userdata, array(
'loginname' => 'testftpx'
))->delete();
$result = json_decode($json_result, true)['data'];
$this->assertEquals('testftpx', $result['loginname']);
}
} }

View File

@@ -50,7 +50,7 @@ class ValidateTest extends TestCase
public function testValidateIp() 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); $this->assertEquals("12.34.56.78", $result);
} }
@@ -58,12 +58,12 @@ class ValidateTest extends TestCase
{ {
$this->expectException("Exception"); $this->expectException("Exception");
$this->expectExceptionCode(400); $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() 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); $this->assertFalse($result);
} }
@@ -71,34 +71,65 @@ class ValidateTest extends TestCase
{ {
$this->expectException("Exception"); $this->expectException("Exception");
$this->expectExceptionCode(400); $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() 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); $this->assertFalse($result);
} }
public function testValidateIpCidr() 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); $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() 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); $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 testValidateIPv6()
{
$result = Validate::is_ipv6('1.1.1.1/4');
$this->assertFalse($result);
$result = Validate::is_ipv6('1.1.1.1');
$this->assertFalse($result);
$result = Validate::is_ipv6('::ffff:10.20.30.40');
$this->assertEquals('::ffff:10.20.30.40', $result);
$result = Validate::is_ipv6('2620:0:2d0:200::7/32');
$this->assertFalse($result);
$result = Validate::is_ipv6('2620:0:2d0:200::7');
$this->assertEquals('2620:0:2d0:200::7', $result);
}
public function testValidateIpLocalhostAllowedWrongIp() public function testValidateIpLocalhostAllowedWrongIp()
{ {
$this->expectException("Exception"); $this->expectException("Exception");
$this->expectExceptionCode(400); $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() public function testValidateUrl()
{ {
$result = Validate::validateUrl("https://froxlor.org/"); $result = Validate::validateUrl("https://froxlor.org/");

View File

@@ -277,4 +277,21 @@ class FtpsTest extends TestCase
$result = json_decode($json_result, true)['data']; $result = json_decode($json_result, true)['data'];
$this->assertEquals('test1ftp2', $result['username']); $this->assertEquals('test1ftp2', $result['username']);
} }
public function testCustomerFtpsDeleteDefaultUser()
{
global $admin_userdata;
// get customer
$json_result = Customers::getLocal($admin_userdata, array(
'loginname' => 'test1'
))->get();
$customer_userdata = json_decode($json_result, true)['data'];
$data = [
'username' => 'test1'
];
$this->expectExceptionCode(400);
$this->expectExceptionMessage('You cannot delete your main FTP account');
Ftps::getLocal($customer_userdata, $data)->delete();
}
} }

View File

@@ -124,4 +124,63 @@ class FroxlorRpcTest extends TestCase
$this->assertEquals('listFunctions', $result['command']['method']); $this->assertEquals('listFunctions', $result['command']['method']);
$this->assertNull($result['params']); $this->assertNull($result['params']);
} }
public function testApiPhpEscaping()
{
$key = $this->generateKey();
$request = array(
'body' => [
'command' => 'Froxlor.listFunctions',
'params' => $key
]
);
$json_request = json_encode($request);
$decoded_request = json_decode($json_request, true);
$decoded_request = $this->stripcslashes_deep($decoded_request);
$this->assertEquals($key['key'], $decoded_request['body']['params']['key']);
$this->assertEquals($key['cert'], $decoded_request['body']['params']['cert']);
}
private function stripcslashes_deep($value)
{
return is_array($value) ? array_map([$this, 'stripcslashes_deep'], $value) : stripcslashes($value);
}
private function generateKey()
{
$dn = array(
"countryName" => "DE",
"stateOrProvinceName" => "Hessen",
"localityName" => "Frankfurt",
"organizationName" => "Froxlor",
"organizationalUnitName" => "Testing",
"commonName" => "test2.local",
"emailAddress" => "team@froxlor.org"
);
// generate key pair
$privkey = openssl_pkey_new(array(
"private_key_bits" => 2048,
"private_key_type" => OPENSSL_KEYTYPE_RSA
));
// generate csr
$csr = openssl_csr_new($dn, $privkey, array(
'digest_alg' => 'sha256'
));
// generate self-signed certificate
$sscert = openssl_csr_sign($csr, null, $privkey, 365, array(
'digest_alg' => 'sha256'
));
// export
openssl_x509_export($sscert, $certout);
openssl_pkey_export($privkey, $pkeyout, null);
return array(
'cert' => $certout,
'key' => $pkeyout
);
}
} }