Merge remote-tracking branch 'origin/2.1.x'
This commit is contained in:
@@ -272,7 +272,8 @@ abstract class ApiCommand extends ApiParameter
|
||||
$ops = [
|
||||
'<',
|
||||
'>',
|
||||
'='
|
||||
'=',
|
||||
'<>'
|
||||
];
|
||||
$first = true;
|
||||
foreach ($search as $field => $valoper) {
|
||||
@@ -396,6 +397,7 @@ abstract class ApiCommand extends ApiParameter
|
||||
|
||||
$nat_fields = [
|
||||
'`c`.`loginname`',
|
||||
'`c`.`name`',
|
||||
'`a`.`loginname`',
|
||||
'`adminname`',
|
||||
'`databasename`',
|
||||
|
||||
@@ -100,7 +100,7 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
AND `id`<> :stdd
|
||||
");
|
||||
$usages_stmt = Database::prepare("
|
||||
SELECT * FROM `" . TABLE_PANEL_DISKSPACE . "`
|
||||
SELECT webspace, mail, mysql FROM `" . TABLE_PANEL_DISKSPACE . "`
|
||||
WHERE `customerid` = :cid
|
||||
ORDER BY `stamp` DESC LIMIT 1
|
||||
");
|
||||
@@ -109,11 +109,10 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
if ($show_usages) {
|
||||
// get number of domains
|
||||
Database::pexecute($domains_stmt, [
|
||||
$domains = Database::pexecute_first($domains_stmt, [
|
||||
'cid' => $row['customerid'],
|
||||
'stdd' => $row['standardsubdomain']
|
||||
]);
|
||||
$domains = $domains_stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$row['domains'] = intval($domains['domains']);
|
||||
// get disk-space usages for web, mysql and mail
|
||||
$usages = Database::pexecute_first($usages_stmt, [
|
||||
@@ -400,6 +399,10 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
}
|
||||
$allowed_phpconfigs = array_map('intval', $allowed_phpconfigs);
|
||||
|
||||
if (empty($allowed_phpconfigs) && $phpenabled == 1) {
|
||||
Response::standardError('customerphpenabledbutnoconfig', '', true);
|
||||
}
|
||||
|
||||
$allowed_mysqlserver = array();
|
||||
if (! empty($p_allowed_mysqlserver) && is_array($p_allowed_mysqlserver)) {
|
||||
foreach ($p_allowed_mysqlserver as $allowed_ms) {
|
||||
@@ -1110,6 +1113,9 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
if (!empty($allowed_phpconfigs)) {
|
||||
$allowed_phpconfigs = array_map('intval', $allowed_phpconfigs);
|
||||
}
|
||||
if (empty($allowed_phpconfigs) && $phpenabled == 1) {
|
||||
Response::standardError('customerphpenabledbutnoconfig', '', true);
|
||||
}
|
||||
|
||||
// add permission for allowed mysql usage if customer was not allowed to use mysql prior
|
||||
if ($result['mysqls'] == 0 && ($mysqls == -1 || $mysqls > 0)) {
|
||||
@@ -1118,6 +1124,7 @@ class Customers extends ApiCommand implements ResourceEntity
|
||||
if (! empty($allowed_mysqlserver)) {
|
||||
$allowed_mysqlserver = array_map('intval', $allowed_mysqlserver);
|
||||
}
|
||||
|
||||
}
|
||||
$def_language = Validate::validate($def_language, 'default language', '', '', [], true);
|
||||
$theme = Validate::validate($theme, 'theme', '', '', [], true);
|
||||
|
||||
@@ -41,20 +41,22 @@ use PDO;
|
||||
/**
|
||||
* @since 0.10.0
|
||||
*/
|
||||
class CustomerBackups extends ApiCommand implements ResourceEntity
|
||||
class DataDump extends ApiCommand implements ResourceEntity
|
||||
{
|
||||
|
||||
/**
|
||||
* add a new customer backup job
|
||||
* add a new data dump job
|
||||
*
|
||||
* @param string $path
|
||||
* path to store the backup to
|
||||
* @param bool $backup_dbs
|
||||
* optional whether to backup databases, default is 0 (false)
|
||||
* @param bool $backup_mail
|
||||
* optional whether to backup mail-data, default is 0 (false)
|
||||
* @param bool $backup_web
|
||||
* optional whether to backup web-data, default is 0 (false)
|
||||
* path to store the dumped data to
|
||||
* @param string $pgp_public_key
|
||||
* optional pgp public key to encrypt the archive, default is empty
|
||||
* @param bool $dump_dbs
|
||||
* optional whether to include databases, default is 0 (false)
|
||||
* @param bool $dump_mail
|
||||
* optional whether to include mail-data, default is 0 (false)
|
||||
* @param bool $dump_web
|
||||
* optional whether to incoude web-data, default is 0 (false)
|
||||
* @param int $customerid
|
||||
* optional, required when called as admin (if $loginname is not specified)
|
||||
* @param string $loginname
|
||||
@@ -72,9 +74,10 @@ class CustomerBackups extends ApiCommand implements ResourceEntity
|
||||
$path = $this->getParam('path');
|
||||
|
||||
// parameter
|
||||
$backup_dbs = $this->getBoolParam('backup_dbs', true, 0);
|
||||
$backup_mail = $this->getBoolParam('backup_mail', true, 0);
|
||||
$backup_web = $this->getBoolParam('backup_web', true, 0);
|
||||
$pgp_public_key = $this->getParam('pgp_public_key', true, '');
|
||||
$dump_dbs = $this->getBoolParam('dump_dbs', true, 0);
|
||||
$dump_mail = $this->getBoolParam('dump_mail', true, 0);
|
||||
$dump_web = $this->getBoolParam('dump_web', true, 0);
|
||||
|
||||
// get customer data
|
||||
$customer = $this->getCustomerData();
|
||||
@@ -86,19 +89,32 @@ class CustomerBackups extends ApiCommand implements ResourceEntity
|
||||
|
||||
// path cannot be the customers docroot
|
||||
if ($path == FileDir::makeCorrectDir($customer['documentroot'])) {
|
||||
Response::standardError('backupfoldercannotbedocroot', '', true);
|
||||
Response::standardError('dumpfoldercannotbedocroot', '', true);
|
||||
}
|
||||
|
||||
if ($backup_dbs != '1') {
|
||||
$backup_dbs = '0';
|
||||
// pgp public key validation
|
||||
if (!empty($pgp_public_key)) {
|
||||
// check if gnupg extension is loaded
|
||||
if (!extension_loaded('gnupg')) {
|
||||
Response::standardError('gnupgextensionnotavailable', '', true);
|
||||
}
|
||||
// check if the pgp public key is a valid key
|
||||
putenv('GNUPGHOME='.sys_get_temp_dir());
|
||||
if (gnupg_import(gnupg_init(), $pgp_public_key) === false) {
|
||||
Response::standardError('invalidpgppublickey', '', true);
|
||||
}
|
||||
}
|
||||
|
||||
if ($backup_mail != '1') {
|
||||
$backup_mail = '0';
|
||||
if ($dump_dbs != '1') {
|
||||
$dump_dbs = '0';
|
||||
}
|
||||
|
||||
if ($backup_web != '1') {
|
||||
$backup_web = '0';
|
||||
if ($dump_mail != '1') {
|
||||
$dump_mail = '0';
|
||||
}
|
||||
|
||||
if ($dump_web != '1') {
|
||||
$dump_web = '0';
|
||||
}
|
||||
|
||||
$task_data = [
|
||||
@@ -107,61 +123,63 @@ class CustomerBackups extends ApiCommand implements ResourceEntity
|
||||
'gid' => $customer['guid'],
|
||||
'loginname' => $customer['loginname'],
|
||||
'destdir' => $path,
|
||||
'backup_dbs' => $backup_dbs,
|
||||
'backup_mail' => $backup_mail,
|
||||
'backup_web' => $backup_web
|
||||
'pgp_public_key' => $pgp_public_key,
|
||||
'dump_dbs' => $dump_dbs,
|
||||
'dump_mail' => $dump_mail,
|
||||
'dump_web' => $dump_web
|
||||
];
|
||||
// schedule backup job
|
||||
Cronjob::inserttask(TaskId::CREATE_CUSTOMER_BACKUP, $task_data);
|
||||
|
||||
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added customer-backup job for '" . $customer['loginname'] . "'. Target directory: " . $userpath);
|
||||
// schedule export job
|
||||
Cronjob::inserttask(TaskId::CREATE_CUSTOMER_DATADUMP, $task_data);
|
||||
|
||||
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] added customer data export job for '" . $customer['loginname'] . "'. Target directory: " . $userpath);
|
||||
return $this->response($task_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* check whether backup is enabled systemwide and if accessible for customer (hide_options)
|
||||
* check whether data dump is enabled systemwide and if accessible for customer (hide_options)
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function validateAccess()
|
||||
{
|
||||
if (Settings::Get('system.backupenabled') != 1) {
|
||||
if (Settings::Get('system.exportenabled') != 1) {
|
||||
throw new Exception("You cannot access this resource", 405);
|
||||
}
|
||||
if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras')) {
|
||||
throw new Exception("You cannot access this resource", 405);
|
||||
}
|
||||
if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras.backup')) {
|
||||
if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'extras.export')) {
|
||||
throw new Exception("You cannot access this resource", 405);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* You cannot get a planned backup.
|
||||
* Try CustomerBackups.listing()
|
||||
* You cannot get a planned data export.
|
||||
* Try DataDump.listing()
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
throw new Exception('You cannot get a planned backup. Try CustomerBackups.listing()', 303);
|
||||
throw new Exception('You cannot get a planned data export. Try DataDump.listing()', 303);
|
||||
}
|
||||
|
||||
/**
|
||||
* You cannot update a planned backup.
|
||||
* You cannot update a planned data export.
|
||||
* You need to delete it and re-add it.
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
throw new Exception('You cannot update a planned backup. You need to delete it and re-add it.', 303);
|
||||
throw new Exception('You cannot update a planned data export. You need to delete it and re-add it.', 303);
|
||||
}
|
||||
|
||||
/**
|
||||
* list all planned backup-jobs, if called from an admin, list all planned backup-jobs of all customers you are
|
||||
* list all planned data export jobs, if called from an admin, list all planned data export jobs of all customers you are
|
||||
* allowed to view, or specify id or loginname for one specific customer
|
||||
*
|
||||
* @param int $customerid
|
||||
* optional, admin-only, select backup-jobs of a specific customer by id
|
||||
* optional, admin-only, select data export jobs of a specific customer by id
|
||||
* @param string $loginname
|
||||
* optional, admin-only, select backup-jobs of a specific customer by loginname
|
||||
* optional, admin-only, select data export jobs of a specific customer by loginname
|
||||
* @param array $sql_search
|
||||
* optional array with index = fieldname, and value = array with 'op' => operator (one of <, > or =),
|
||||
* LIKE is used if left empty and 'value' => searchvalue
|
||||
@@ -181,9 +199,9 @@ class CustomerBackups extends ApiCommand implements ResourceEntity
|
||||
{
|
||||
$this->validateAccess();
|
||||
|
||||
$customer_ids = $this->getAllowedCustomerIds('extras.backup');
|
||||
$customer_ids = $this->getAllowedCustomerIds('extras.export');
|
||||
|
||||
// check whether there is a backup-job for this customer
|
||||
// check whether there is a data export job for this customer
|
||||
$query_fields = [];
|
||||
$sel_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20'" . $this->getSearchWhere($query_fields, true) . $this->getOrderBy() . $this->getLimit());
|
||||
Database::pexecute($sel_stmt, $query_fields, true, true);
|
||||
@@ -194,7 +212,7 @@ class CustomerBackups extends ApiCommand implements ResourceEntity
|
||||
$result[] = $entry;
|
||||
}
|
||||
}
|
||||
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list customer-backups");
|
||||
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list customer data dump jobs");
|
||||
return $this->response([
|
||||
'count' => count($result),
|
||||
'list' => $result
|
||||
@@ -202,12 +220,12 @@ class CustomerBackups extends ApiCommand implements ResourceEntity
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the total number of planned backups
|
||||
* returns the total number of planned data exports
|
||||
*
|
||||
* @param int $customerid
|
||||
* optional, admin-only, select backup-jobs of a specific customer by id
|
||||
* optional, admin-only, select data export jobs of a specific customer by id
|
||||
* @param string $loginname
|
||||
* optional, admin-only, select backup-jobs of a specific customer by loginname
|
||||
* optional, admin-only, select data export jobs of a specific customer by loginname
|
||||
*
|
||||
* @access admin, customer
|
||||
* @return string json-encoded response message
|
||||
@@ -217,9 +235,9 @@ class CustomerBackups extends ApiCommand implements ResourceEntity
|
||||
{
|
||||
$this->validateAccess();
|
||||
|
||||
$customer_ids = $this->getAllowedCustomerIds('extras.backup');
|
||||
$customer_ids = $this->getAllowedCustomerIds('extras.export');
|
||||
|
||||
// check whether there is a backup-job for this customer
|
||||
// check whether there is a data export job for this customer
|
||||
$result_count = 0;
|
||||
$sel_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20'");
|
||||
Database::pexecute($sel_stmt, null, true, true);
|
||||
@@ -233,10 +251,10 @@ class CustomerBackups extends ApiCommand implements ResourceEntity
|
||||
}
|
||||
|
||||
/**
|
||||
* delete a planned backup-jobs by id, if called from an admin you need to specify the customerid/loginname
|
||||
* delete a planned data export jobs by id, if called from an admin you need to specify the customerid/loginname
|
||||
*
|
||||
* @param int $backup_job_entry
|
||||
* id of backup job
|
||||
* @param int $job_entry
|
||||
* id of data export job
|
||||
* @param int $customerid
|
||||
* optional, required when called as admin (if $loginname is not specified)
|
||||
* @param string $loginname
|
||||
@@ -248,26 +266,26 @@ class CustomerBackups extends ApiCommand implements ResourceEntity
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
// get planned backups
|
||||
$result = $this->apiCall('CustomerBackups.listing', $this->getParamList());
|
||||
// get planned exports
|
||||
$result = $this->apiCall('DataDump.listing', $this->getParamList());
|
||||
|
||||
$entry = $this->getParam('backup_job_entry');
|
||||
$customer_ids = $this->getAllowedCustomerIds('extras.backup');
|
||||
$entry = $this->getParam('job_entry');
|
||||
$customer_ids = $this->getAllowedCustomerIds('extras.export');
|
||||
|
||||
if ($result['count'] > 0 && $entry > 0) {
|
||||
// prepare statement
|
||||
$del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `id` = :tid");
|
||||
// check for the correct job
|
||||
foreach ($result['list'] as $backupjob) {
|
||||
if ($backupjob['id'] == $entry && in_array($backupjob['data']['customerid'], $customer_ids)) {
|
||||
foreach ($result['list'] as $exportjob) {
|
||||
if ($exportjob['id'] == $entry && in_array($exportjob['data']['customerid'], $customer_ids)) {
|
||||
Database::pexecute($del_stmt, [
|
||||
'tid' => $entry
|
||||
], true, true);
|
||||
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] deleted planned customer-backup #" . $entry);
|
||||
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_NOTICE, "[API] deleted planned customer data export job #" . $entry);
|
||||
return $this->response(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Exception('Backup job with id #' . $entry . ' could not be found', 404);
|
||||
throw new Exception('Data export job with id #' . $entry . ' could not be found', 404);
|
||||
}
|
||||
}
|
||||
@@ -302,6 +302,8 @@ class DomainZones extends ApiCommand implements ResourceEntity
|
||||
}
|
||||
} elseif ($type == 'SSHFP' && !empty($content)) {
|
||||
$content = $content;
|
||||
} elseif ($type == 'TLSA' && !empty($content)) {
|
||||
$content = $content;
|
||||
} elseif ($type == 'TXT' && !empty($content)) {
|
||||
// check that TXT content is enclosed in " "
|
||||
$content = Dns::encloseTXTContent($content);
|
||||
|
||||
@@ -76,7 +76,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$query_fields = [];
|
||||
$result_stmt = Database::prepare("
|
||||
SELECT
|
||||
`d`.*, `c`.`loginname`, `c`.`deactivated`, `c`.`name`, `c`.`firstname`, `c`.`company`, `c`.`standardsubdomain`, `c`.`adminid` as customeradmin,
|
||||
`d`.*, `c`.`loginname`, `c`.`deactivated` as `customer_deactivated`, `c`.`name`, `c`.`firstname`, `c`.`company`, `c`.`standardsubdomain`, `c`.`adminid` as customeradmin,
|
||||
`ad`.`id` AS `aliasdomainid`, `ad`.`domain` AS `aliasdomain`
|
||||
FROM `" . TABLE_PANEL_DOMAINS . "` `d`
|
||||
LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` `c` USING(`customerid`)
|
||||
@@ -110,7 +110,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
*
|
||||
* @param number $domain_id
|
||||
* @param bool $ssl_only
|
||||
* optional, return only ssl enabled ip's, default false
|
||||
* optional, return only ssl enabled ips, default false
|
||||
* @return array
|
||||
*/
|
||||
private function getIpsForDomain($domain_id = 0, $ssl_only = false)
|
||||
@@ -190,9 +190,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
* optional, whether to create an exclusive web-logfile for this domain, default 0 (false)
|
||||
* @param int $alias
|
||||
* optional, domain-id of a domain that the new domain should be an alias of, default 0 (none)
|
||||
* @param int $issubof
|
||||
* optional, domain-id of a domain this domain is a subdomain of (required for webserver-cronjob to
|
||||
* generate the correct order), default 0 (none)
|
||||
* @param string $registration_date
|
||||
* optional, date of domain registration in form of YYYY-MM-DD, default empty (none)
|
||||
* @param string $termination_date
|
||||
@@ -210,7 +207,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
* @param string $ssl_specialsettings
|
||||
* optional, custom webserver vhost-content which is added to the generated ssl-vhost, default empty
|
||||
* @param bool $include_specialsettings
|
||||
* optional, whether or not to include non-ssl specialsettings in the generated ssl-vhost, default false
|
||||
* optional, whether to include non-ssl specialsettings in the generated ssl-vhost, default false
|
||||
* @param bool $notryfiles
|
||||
* optional, [nginx only] do not generate the default try-files directive, default 0 (false)
|
||||
* @param bool $writeaccesslog
|
||||
@@ -219,7 +216,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
* optional, Enable writing an error-log file for this domain, default 1 (true)
|
||||
* @param string $documentroot
|
||||
* optional, specify homedir of domain by specifying a directory (relative to customer-docroot), be
|
||||
* aware, if path starts with / it it considered a full path, not relative to customer-docroot. Also
|
||||
* aware, if path starts with / it is considered a full path, not relative to customer-docroot. Also
|
||||
* specifying a URL is possible here (redirect), default empty (autogenerated)
|
||||
* @param bool $phpenabled
|
||||
* optional, whether php is enabled for this domain, default 0 (false)
|
||||
@@ -244,7 +241,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
* optional, do NOT set the systems default ssl ip addresses if none are given via $ssl_ipandport
|
||||
* parameter
|
||||
* @param bool $sslenabled
|
||||
* optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default
|
||||
* optional, whether SSL is enabled for this domain, regardless of the assigned ssl-ips, default
|
||||
* 1 (true)
|
||||
* @param bool $http2
|
||||
* optional, whether to enable http/2 for this domain (requires to be enabled in the settings), default
|
||||
@@ -252,9 +249,9 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
* @param int $hsts_maxage
|
||||
* optional max-age value for HSTS header
|
||||
* @param bool $hsts_sub
|
||||
* optional whether or not to add subdomains to the HSTS header
|
||||
* optional whether to add subdomains to the HSTS header
|
||||
* @param bool $hsts_preload
|
||||
* optional whether or not to preload HSTS header value
|
||||
* optional whether to preload HSTS header value
|
||||
* @param bool $ocsp_stapling
|
||||
* optional whether to enable ocsp-stapling for this domain. default 0 (false), requires SSL
|
||||
* @param bool $honorcipherorder
|
||||
@@ -263,7 +260,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
* optional whether to enable or disable TLS sessiontickets (RFC 5077) for this domain. default 1
|
||||
* (true), requires SSL
|
||||
* @param bool $override_tls
|
||||
* optional whether or not to override system-tls settings like protocol, ssl-ciphers and if applicable
|
||||
* optional whether to override system-tls settings like protocol, ssl-ciphers and if applicable
|
||||
* tls-1.3 ciphers, requires change_serversettings flag for the admin, default false
|
||||
* @param array $ssl_protocols
|
||||
* optional list of allowed/used ssl/tls protocols, see system.ssl_protocols setting, only used/required
|
||||
@@ -298,7 +295,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$serveraliasoption = $this->getParam('selectserveralias', true, Settings::Get('system.domaindefaultalias'));
|
||||
$speciallogfile = $this->getBoolParam('speciallogfile', true, 0);
|
||||
$aliasdomain = intval($this->getParam('alias', true, 0));
|
||||
$issubof = $this->getParam('issubof', true, 0);
|
||||
$registration_date = $this->getParam('registration_date', true, '');
|
||||
$termination_date = $this->getParam('termination_date', true, '');
|
||||
$caneditdomain = $this->getBoolParam('caneditdomain', true, 0);
|
||||
@@ -320,9 +316,9 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, -1);
|
||||
$ssl_redirect = $this->getBoolParam('ssl_redirect', true, 0);
|
||||
$letsencrypt = $this->getBoolParam('letsencrypt', true, 0);
|
||||
$sslenabled = $this->getBoolParam('sslenabled', true, 1);
|
||||
$dont_use_default_ssl_ipandport_if_empty = $this->getBoolParam('dont_use_default_ssl_ipandport_if_empty', true, 0);
|
||||
$p_ssl_ipandports = $this->getParam('ssl_ipandport', true, $dont_use_default_ssl_ipandport_if_empty ? [] : explode(',', Settings::Get('system.defaultsslip')));
|
||||
$sslenabled = $this->getBoolParam('sslenabled', true, 1);
|
||||
$http2 = $this->getBoolParam('http2', true, 0);
|
||||
$hsts_maxage = $this->getParam('hsts_maxage', true, 0);
|
||||
$hsts_sub = $this->getBoolParam('hsts_sub', true, 0);
|
||||
@@ -548,6 +544,10 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$ssl_specialsettings = Validate::validate(str_replace("\r\n", "\n", $ssl_specialsettings), 'ssl_specialsettings', '/^[^\0]*$/', '', [], true);
|
||||
}
|
||||
}
|
||||
if (Settings::Get('system.use_ssl') == "1" && $sslenabled == 1 && empty($ssl_ipandports)) {
|
||||
// enabled ssl for the domain but no ssl ip/port is selected
|
||||
Response::standardError('nosslippportgiven', '', true);
|
||||
}
|
||||
if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports)) {
|
||||
$ssl_redirect = 0;
|
||||
$letsencrypt = 0;
|
||||
@@ -665,10 +665,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$serveraliasoption = '0';
|
||||
}
|
||||
|
||||
if ($issubof <= 0) {
|
||||
$issubof = '0';
|
||||
}
|
||||
|
||||
$idna_convert = new IdnaWrapper();
|
||||
if ($domain == '') {
|
||||
Response::standardError([
|
||||
@@ -723,7 +719,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
'phpsettingid' => $phpsettingid,
|
||||
'mod_fcgid_starter' => $mod_fcgid_starter,
|
||||
'mod_fcgid_maxrequests' => $mod_fcgid_maxrequests,
|
||||
'ismainbutsubto' => $issubof,
|
||||
'letsencrypt' => $letsencrypt,
|
||||
'http2' => $http2,
|
||||
'hsts' => $hsts_maxage,
|
||||
@@ -777,7 +772,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
`phpsettingid` = :phpsettingid,
|
||||
`mod_fcgid_starter` = :mod_fcgid_starter,
|
||||
`mod_fcgid_maxrequests` = :mod_fcgid_maxrequests,
|
||||
`ismainbutsubto` = :ismainbutsubto,
|
||||
`letsencrypt` = :letsencrypt,
|
||||
`http2` = :http2,
|
||||
`hsts` = :hsts,
|
||||
@@ -1069,9 +1063,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
* default 0 (false)
|
||||
* @param int $alias
|
||||
* optional, domain-id of a domain that the new domain should be an alias of, default 0 (none)
|
||||
* @param int $issubof
|
||||
* optional, domain-id of a domain this domain is a subdomain of (required for webserver-cronjob to
|
||||
* generate the correct order), default 0 (none)
|
||||
* @param string $registration_date
|
||||
* optional, date of domain registration in form of YYYY-MM-DD, default empty (none)
|
||||
* @param string $termination_date
|
||||
@@ -1089,7 +1080,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
* @param string $ssl_specialsettings
|
||||
* optional, custom webserver vhost-content which is added to the generated ssl-vhost, default empty
|
||||
* @param bool $include_specialsettings
|
||||
* optional, whether or not to include non-ssl specialsettings in the generated ssl-vhost, default false
|
||||
* optional, whether to include non-ssl specialsettings in the generated ssl-vhost, default false
|
||||
* @param bool $specialsettingsforsubdomains
|
||||
* optional, whether to apply specialsettings to all subdomains of this domain, default is read from
|
||||
* setting system.apply_specialsettings_default
|
||||
@@ -1101,7 +1092,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
* optional, Enable writing an error-log file for this domain, default 1 (true)
|
||||
* @param string $documentroot
|
||||
* optional, specify homedir of domain by specifying a directory (relative to customer-docroot), be
|
||||
* aware, if path starts with / it it considered a full path, not relative to customer-docroot. Also
|
||||
* aware, if path starts with / it is considered a full path, not relative to customer-docroot. Also
|
||||
* specifying a URL is possible here (redirect), default empty (autogenerated)
|
||||
* @param bool $phpenabled
|
||||
* optional, whether php is enabled for this domain, default 0 (false)
|
||||
@@ -1130,7 +1121,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
* optional, if set to true and no $ssl_ipandport value is given, the ip's get removed, otherwise, the
|
||||
* currently set value is used, default false
|
||||
* @param bool $sslenabled
|
||||
* optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default
|
||||
* optional, whether SSL is enabled for this domain, regardless of the assigned ssl-ips, default
|
||||
* 1 (true)
|
||||
* @param bool $http2
|
||||
* optional, whether to enable http/2 for this domain (requires to be enabled in the settings), default
|
||||
@@ -1138,9 +1129,9 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
* @param int $hsts_maxage
|
||||
* optional max-age value for HSTS header
|
||||
* @param bool $hsts_sub
|
||||
* optional whether or not to add subdomains to the HSTS header
|
||||
* optional whether to add subdomains to the HSTS header
|
||||
* @param bool $hsts_preload
|
||||
* optional whether or not to preload HSTS header value
|
||||
* optional whether to preload HSTS header value
|
||||
* @param bool $ocsp_stapling
|
||||
* optional whether to enable ocsp-stapling for this domain. default 0 (false), requires SSL
|
||||
* @param bool $honorcipherorder
|
||||
@@ -1150,6 +1141,8 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
* (true), requires SSL
|
||||
* @param string $description
|
||||
* optional custom description (currently not used/shown in the frontend), default empty
|
||||
* @param bool $deactivated
|
||||
* optional, if 1 (true) the domain can be deactivated/suspended
|
||||
*
|
||||
* @access admin
|
||||
* @return string json-encoded array
|
||||
@@ -1191,7 +1184,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$speciallogfile = $this->getBoolParam('speciallogfile', true, $result['speciallogfile']);
|
||||
$speciallogverified = $this->getBoolParam('speciallogverified', true, 0);
|
||||
$aliasdomain = intval($this->getParam('alias', true, $result['aliasdomain']));
|
||||
$issubof = $this->getParam('issubof', true, $result['ismainbutsubto']);
|
||||
$registration_date = $this->getParam('registration_date', true, $result['registration_date']);
|
||||
$termination_date = $this->getParam('termination_date', true, $result['termination_date']);
|
||||
$caneditdomain = $this->getBoolParam('caneditdomain', true, $result['caneditdomain']);
|
||||
@@ -1219,7 +1211,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$p_ssl_ipandports = $this->getParam('ssl_ipandport', true, $remove_ssl_ipandport ? [
|
||||
-1
|
||||
] : null);
|
||||
$sslenabled = $this->getBoolParam('sslenabled', true, $result['ssl_enabled']);
|
||||
$sslenabled = $remove_ssl_ipandport ? false : $this->getBoolParam('sslenabled', true, $result['ssl_enabled']);
|
||||
$http2 = $this->getBoolParam('http2', true, $result['http2']);
|
||||
$hsts_maxage = $this->getParam('hsts_maxage', true, $result['hsts']);
|
||||
$hsts_sub = $this->getBoolParam('hsts_sub', true, $result['hsts_sub']);
|
||||
@@ -1246,6 +1238,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$tlsv13_cipher_list = $result['tlsv13_cipher_list'];
|
||||
}
|
||||
$description = $this->getParam('description', true, $result['description']);
|
||||
$deactivated = $this->getBoolParam('deactivated', true, $result['deactivated']);
|
||||
|
||||
// count subdomain usage of source-domain
|
||||
$subdomains_stmt = Database::prepare("
|
||||
@@ -1528,6 +1521,10 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
if ($remove_ssl_ipandport || (!empty($p_ssl_ipandports) && $p_ssl_ipandports[0] == -1)) {
|
||||
$ssl_ipandports = [];
|
||||
}
|
||||
if (Settings::Get('system.use_ssl') == "1" && $sslenabled && empty($ssl_ipandports)) {
|
||||
// enabled ssl for the domain but no ssl ip/port is selected
|
||||
Response::standardError('nosslippportgiven', '', true);
|
||||
}
|
||||
if (Settings::Get('system.use_ssl') == "0" || empty($ssl_ipandports)) {
|
||||
$ssl_redirect = 0;
|
||||
$letsencrypt = 0;
|
||||
@@ -1564,7 +1561,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
}
|
||||
|
||||
// Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated
|
||||
if ($ssl_redirect > 0 && $letsencrypt == 1 && $result['letsencrypt'] != $letsencrypt) {
|
||||
if (($result['letsencrypt'] != $letsencrypt || $result['ssl_redirect'] != $ssl_redirect) && $ssl_redirect > 0 && $letsencrypt == 1) {
|
||||
$ssl_redirect = 2;
|
||||
}
|
||||
|
||||
@@ -1640,10 +1637,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
Response::standardError('domainisaliasorothercustomer', '', true);
|
||||
}
|
||||
|
||||
if ($issubof <= 0) {
|
||||
$issubof = '0';
|
||||
}
|
||||
|
||||
if ($serveraliasoption != '1' && $serveraliasoption != '2') {
|
||||
$serveraliasoption = '0';
|
||||
}
|
||||
@@ -1666,7 +1659,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
|| $writeaccesslog != $result['writeaccesslog']
|
||||
|| $writeerrorlog != $result['writeerrorlog']
|
||||
|| $aliasdomain != $result['aliasdomain']
|
||||
|| $issubof != $result['ismainbutsubto']
|
||||
|| $email_only != $result['email_only']
|
||||
|| ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1')
|
||||
|| $letsencrypt != $result['letsencrypt']
|
||||
@@ -1837,7 +1829,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$update_data['writeerrorlog'] = $writeerrorlog;
|
||||
$update_data['registration_date'] = $registration_date;
|
||||
$update_data['termination_date'] = $termination_date;
|
||||
$update_data['ismainbutsubto'] = $issubof;
|
||||
$update_data['letsencrypt'] = $letsencrypt;
|
||||
$update_data['http2'] = $http2;
|
||||
$update_data['hsts'] = $hsts_maxage;
|
||||
@@ -1852,6 +1843,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$update_data['honorcipherorder'] = $honorcipherorder;
|
||||
$update_data['sessiontickets'] = $sessiontickets;
|
||||
$update_data['description'] = $description;
|
||||
$update_data['deactivated'] = $deactivated;
|
||||
$update_data['id'] = $id;
|
||||
|
||||
$update_stmt = Database::prepare("
|
||||
@@ -1885,7 +1877,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
`writeerrorlog` = :writeerrorlog,
|
||||
`registration_date` = :registration_date,
|
||||
`termination_date` = :termination_date,
|
||||
`ismainbutsubto` = :ismainbutsubto,
|
||||
`letsencrypt` = :letsencrypt,
|
||||
`http2` = :http2,
|
||||
`hsts` = :hsts,
|
||||
@@ -1899,11 +1890,36 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
`ssl_enabled` = :sslenabled,
|
||||
`ssl_honorcipherorder` = :honorcipherorder,
|
||||
`ssl_sessiontickets` = :sessiontickets,
|
||||
`description` = :description
|
||||
`description` = :description,
|
||||
`deactivated` = :deactivated
|
||||
WHERE `id` = :id
|
||||
");
|
||||
Database::pexecute($update_stmt, $update_data, true, true);
|
||||
|
||||
// activate/deactivate domain-based services
|
||||
if ($deactivated != $result['deactivated']) {
|
||||
// deactivate email accounts
|
||||
$yesno = ($deactivated ? 'N' : 'Y');
|
||||
$pop3 = ($deactivated ? '0' : (int)$customer['pop3']);
|
||||
$imap = ($deactivated ? '0' : (int)$customer['imap']);
|
||||
|
||||
$upd_stmt = Database::prepare("
|
||||
UPDATE `" . TABLE_MAIL_USERS . "`
|
||||
SET `postfix`= :yesno, `pop3` = :pop3, `imap` = :imap
|
||||
WHERE `customerid` = :customerid AND `domainid` = :domainid
|
||||
");
|
||||
Database::pexecute($upd_stmt, [
|
||||
'yesno' => $yesno,
|
||||
'pop3' => $pop3,
|
||||
'imap' => $imap,
|
||||
'customerid' => $customerid,
|
||||
'domainid' => $id
|
||||
]);
|
||||
|
||||
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] " . ($deactivated ? 'deactivated' : 'reactivated') . " domain '" . $result['domain'] . "'");
|
||||
Cronjob::inserttask(TaskId::REBUILD_VHOST);
|
||||
}
|
||||
|
||||
$_update_data['customerid'] = $customerid;
|
||||
$_update_data['adminid'] = $adminid;
|
||||
$_update_data['phpenabled'] = $phpenabled;
|
||||
@@ -1921,6 +1937,7 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$_update_data['honorcipherorder'] = $honorcipherorder;
|
||||
$_update_data['sessiontickets'] = $sessiontickets;
|
||||
$_update_data['parentdomainid'] = $id;
|
||||
$_update_data['deactivated'] = $deactivated;
|
||||
|
||||
// if php config is to be set for all subdomains, check here
|
||||
$update_phpconfig = '';
|
||||
@@ -1953,7 +1970,8 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
`ssl_cipher_list` = :ssl_cipher_list,
|
||||
`tlsv13_cipher_list` = :tlsv13_cipher_list,
|
||||
`ssl_honorcipherorder` = :honorcipherorder,
|
||||
`ssl_sessiontickets` = :sessiontickets
|
||||
`ssl_sessiontickets` = :sessiontickets,
|
||||
`deactivated` = :deactivated
|
||||
" . $update_phpconfig . $upd_specialsettings . $updatechildren . $update_sslredirect . "
|
||||
WHERE `parentdomainid` = :parentdomainid
|
||||
");
|
||||
@@ -2073,9 +2091,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
* optional, the domain-id
|
||||
* @param string $domainname
|
||||
* optional, the domainname
|
||||
* @param bool $delete_mainsubdomains
|
||||
* optional, remove also domains that are subdomains of this domain but added as main domains; default
|
||||
* false
|
||||
* @param bool $is_stdsubdomain
|
||||
* optional, default false, specify whether it's a std-subdomain you are deleting as it does not count
|
||||
* as subdomain-resource
|
||||
@@ -2091,7 +2106,6 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$dn_optional = $id > 0;
|
||||
$domainname = $this->getParam('domainname', $dn_optional, '');
|
||||
$is_stdsubdomain = $this->getParam('is_stdsubdomain', true, 0);
|
||||
$remove_subbutmain_domains = $this->getParam('delete_mainsubdomains', true, 0);
|
||||
|
||||
$result = $this->apiCall('Domains.get', [
|
||||
'id' => $id,
|
||||
@@ -2099,15 +2113,10 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
]);
|
||||
$id = $result['id'];
|
||||
|
||||
// check for deletion of main-domains which are logically subdomains, #329
|
||||
$rsd_sql = '';
|
||||
if ($remove_subbutmain_domains) {
|
||||
$rsd_sql .= " OR `ismainbutsubto` = :id";
|
||||
}
|
||||
|
||||
$subresult_stmt = Database::prepare("
|
||||
SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "`
|
||||
WHERE (`id` = :id OR `parentdomainid` = :id " . $rsd_sql . ")");
|
||||
SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "`
|
||||
WHERE (`id` = :id OR `parentdomainid` = :id)
|
||||
");
|
||||
Database::pexecute($subresult_stmt, [
|
||||
'id' => $id
|
||||
], true, true);
|
||||
@@ -2129,23 +2138,10 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] deleted domain/s from mail-tables");
|
||||
}
|
||||
|
||||
// if mainbutsubto-domains are not to be deleted, re-assign the (ismainbutsubto value of the main
|
||||
// domain which is being deleted) as their new ismainbutsubto value
|
||||
if ($remove_subbutmain_domains !== 1) {
|
||||
$upd_stmt = Database::prepare("
|
||||
UPDATE `" . TABLE_PANEL_DOMAINS . "` SET
|
||||
`ismainbutsubto` = :newIsMainButSubtoValue
|
||||
WHERE `ismainbutsubto` = :deletedMainDomainId
|
||||
");
|
||||
Database::pexecute($upd_stmt, [
|
||||
'newIsMainButSubtoValue' => $result['ismainbutsubto'],
|
||||
'deletedMainDomainId' => $id
|
||||
], true, true);
|
||||
}
|
||||
|
||||
$del_stmt = Database::prepare("
|
||||
DELETE FROM `" . TABLE_PANEL_DOMAINS . "`
|
||||
WHERE `id` = :id OR `parentdomainid` = :id " . $rsd_sql);
|
||||
DELETE FROM `" . TABLE_PANEL_DOMAINS . "`
|
||||
WHERE `id` = :id OR `parentdomainid` = :id
|
||||
");
|
||||
Database::pexecute($del_stmt, [
|
||||
'id' => $id
|
||||
], true, true);
|
||||
@@ -2230,4 +2226,118 @@ class Domains extends ApiCommand implements ResourceEntity
|
||||
}
|
||||
throw new Exception("Not allowed to execute given command.", 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* duplicate domain entry by either id or domainname. All parameters from Domains.add() can be used
|
||||
* to overwrite source entity values if necessary.
|
||||
*
|
||||
* @param int $id
|
||||
* optional, the domain-id
|
||||
* @param string $domainname
|
||||
* optional, the domainname
|
||||
* @param string $domain
|
||||
* required, name of the new domain to be added
|
||||
*
|
||||
* @access admin
|
||||
* @return string json-encoded array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function duplicate()
|
||||
{
|
||||
if ($this->isAdmin()) {
|
||||
// parameters
|
||||
$id = $this->getParam('id', true, 0);
|
||||
$dn_optional = $id > 0;
|
||||
$domainname = $this->getParam('domainname', $dn_optional, '');
|
||||
$p_domain = $this->getParam('domain');
|
||||
|
||||
// get requested domain
|
||||
$result = $this->apiCall('Domains.get', [
|
||||
'id' => $id,
|
||||
'domainname' => $domainname,
|
||||
]);
|
||||
|
||||
// clear some defaults
|
||||
unset($result['domain_ace']);
|
||||
unset($result['adminid']);
|
||||
unset($result['documentroot']);
|
||||
unset($result['registration_date']);
|
||||
unset($result['termination_date']);
|
||||
unset($result['zonefile']);
|
||||
// clear auto-generated values
|
||||
unset($result['bindserial']);
|
||||
unset($result['dkim_privkey']);
|
||||
unset($result['dkim_pubkey']);
|
||||
// clear api-call generated fields
|
||||
unset($result['domain_hascert']);
|
||||
|
||||
// set correct ip/port information
|
||||
$domain_ips = $result['ipsandports'];
|
||||
unset($result['ipsandports']);
|
||||
$result['ipandport'] = [];
|
||||
$result['ssl_ipandport'] = [];
|
||||
foreach ($domain_ips as $dip) {
|
||||
if ($dip['ssl'] == 1) {
|
||||
$result['ssl_ipandport'][] = $dip['id'];
|
||||
} else {
|
||||
$result['ipandport'][] = $dip['id'];
|
||||
}
|
||||
}
|
||||
|
||||
// check whether we are changing the customer/owner
|
||||
if ($this->getParam('customerid', true, 0) == 0 && $this->getParam('loginname', true, '') == '') {
|
||||
$customerid = $result['customerid'];
|
||||
} else {
|
||||
$customer = $this->getCustomerData();
|
||||
$customerid = $customer['customerid'];
|
||||
}
|
||||
|
||||
// check for alias-domain and whether it belongs to the target user
|
||||
if (!empty($result['aliasdomain']) && $customerid == $result['customerid']) {
|
||||
// duplicate alias entry
|
||||
$result['alias'] = $result['aliasdomain'];
|
||||
}
|
||||
unset($result['aliasdomain']);
|
||||
|
||||
// validate possible fpm configs and whether the customer is allowed to use them
|
||||
if ($customerid != $result['customerid']) {
|
||||
$allowed_phpconfigs = json_decode($customer['allowed_phpconfigs'] ?? '[]', true);
|
||||
if (empty($allowed_phpconfigs)) {
|
||||
// system defaults
|
||||
unset($result['phpsettingid']);
|
||||
} elseif (!in_array($result['phpsettingid'], $allowed_phpconfigs)) {
|
||||
// use the first customer allowed config
|
||||
$result['phpsettingid'] = array_shift($allowed_phpconfigs);
|
||||
}
|
||||
}
|
||||
|
||||
// translate serveralias values
|
||||
$result['selectserveralias'] = 2;
|
||||
if ((int)$result['wwwserveralias'] == 1) {
|
||||
$result['selectserveralias'] = 1;
|
||||
} elseif ((int)$result['iswildcarddomain'] == 1) {
|
||||
$result['selectserveralias'] = 0;
|
||||
}
|
||||
unset($result['wwwserveralias']);
|
||||
unset($result['iswildcarddomain']);
|
||||
|
||||
// translate sslenabled flag
|
||||
$result['sslenabled'] = $result['ssl_enabled'];
|
||||
unset($result['ssl_enabled']);
|
||||
|
||||
$additional_params = $this->getParamList();
|
||||
// unset unneeded params from this call
|
||||
unset($additional_params['id']);
|
||||
unset($additional_params['domainname']);
|
||||
unset($additional_params['domain']);
|
||||
|
||||
// set new values and merge with optional add() parameters
|
||||
$new_domain = array_merge($result, $additional_params);
|
||||
$new_domain['domain'] = $p_domain;
|
||||
|
||||
$result_new = $this->apiCall('Domains.add', $new_domain);
|
||||
return $this->response($result_new);
|
||||
}
|
||||
throw new Exception("Not allowed to execute given command.", 403);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,9 +95,13 @@ class EmailAccounts extends ApiCommand implements ResourceEntity
|
||||
$customer = $this->getCustomerData('email_accounts');
|
||||
|
||||
// check for imap||pop3 == 1, see #1298
|
||||
// d00p, 6.5.2023 @revert this - if a customer has resources which allow email accounts
|
||||
// it implicitly allowed SMTP, e.g. sending of emails which also requires an account to exist
|
||||
/*
|
||||
if ($customer['imap'] != '1' && $customer['pop3'] != '1') {
|
||||
Response::standardError('notallowedtouseaccounts', '', true);
|
||||
}
|
||||
*/
|
||||
|
||||
if (!empty($emailaddr)) {
|
||||
$idna_convert = new IdnaWrapper();
|
||||
|
||||
@@ -88,9 +88,12 @@ class Emails extends ApiCommand implements ResourceEntity
|
||||
$domain_check = $this->apiCall('SubDomains.get', [
|
||||
'domainname' => $domain
|
||||
], true);
|
||||
if ($domain_check['isemaildomain'] == 0) {
|
||||
if ((int)$domain_check['isemaildomain'] == 0) {
|
||||
Response::standardError('maindomainnonexist', $domain, true);
|
||||
}
|
||||
if ((int)$domain_check['deactivated'] == 1) {
|
||||
Response::standardError('maindomaindeactivated', $domain, true);
|
||||
}
|
||||
|
||||
if (Settings::Get('catchall.catchall_enabled') != '1') {
|
||||
$iscatchall = 0;
|
||||
|
||||
@@ -202,7 +202,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
|
||||
|
||||
// validation
|
||||
$description = Validate::validate($description, 'description', Validate::REGEX_DESC_TEXT, '', [], true);
|
||||
$reload_cmd = Validate::validate($reload_cmd, 'reload_cmd', '/^[a-z0-9\/\._\- ]+$/i', '', [], true);
|
||||
$reload_cmd = Validate::validate($reload_cmd, 'reload_cmd', '/^[a-z0-9\/\._\-@ ]+$/i', '', [], true);
|
||||
$sel_stmt = Database::prepare("SELECT `id` FROM `".TABLE_PANEL_FPMDAEMONS."` WHERE `reload_cmd` = :rc");
|
||||
$dupcheck = Database::pexecute_first($sel_stmt, ['rc' => $reload_cmd]);
|
||||
if ($dupcheck && $dupcheck['id']) {
|
||||
@@ -327,7 +327,7 @@ class FpmDaemons extends ApiCommand implements ResourceEntity
|
||||
|
||||
// validation
|
||||
$description = Validate::validate($description, 'description', Validate::REGEX_DESC_TEXT, '', [], true);
|
||||
$reload_cmd = Validate::validate($reload_cmd, 'reload_cmd', '/^[a-z0-9\/\._\- ]+$/i', '', [], true);
|
||||
$reload_cmd = Validate::validate($reload_cmd, 'reload_cmd', '/^[a-z0-9\/\._\-@ ]+$/i', '', [], true);
|
||||
$sel_stmt = Database::prepare("SELECT `id` FROM `".TABLE_PANEL_FPMDAEMONS."` WHERE `reload_cmd` = :rc");
|
||||
$dupcheck = Database::pexecute_first($sel_stmt, ['rc' => $reload_cmd]);
|
||||
if ($dupcheck && $dupcheck['id'] != $id) {
|
||||
|
||||
@@ -37,6 +37,7 @@ use Froxlor\Settings;
|
||||
use Froxlor\SImExporter;
|
||||
use Froxlor\System\Cronjob;
|
||||
use Froxlor\System\Crypt;
|
||||
use Froxlor\Validate\Validate;
|
||||
use PDO;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
@@ -269,6 +270,79 @@ class Froxlor extends ApiCommand
|
||||
return $this->response(Crypt::generatePassword($length));
|
||||
}
|
||||
|
||||
/**
|
||||
* return a one-time login link URL for a given user
|
||||
*
|
||||
* @param int $customerid optional, required if $loginname is not specified, user to create link for
|
||||
* @param string $loginname optional, required if $customerid is not specified, user to create link for
|
||||
* @param int $valid_time optional, value in seconds how long the link will be valid, default is 10 seconds, valid values are numbers from 10 to 120
|
||||
* @param string $allowed_from optional, comma separated list of ip addresses or networks to allow login from via this link
|
||||
*
|
||||
* @access admin
|
||||
* @return string json-encoded array [base => domain, uri => relative link]
|
||||
* @throws Exception
|
||||
*/
|
||||
public function generateLoginLink()
|
||||
{
|
||||
if ($this->isAdmin()) {
|
||||
$customer = $this->getCustomerData();
|
||||
|
||||
// cannot create link for deactivated users
|
||||
if ((int)$customer['deactivated'] == 1) {
|
||||
throw new Exception("Cannot generate link for deactivated user", 406);
|
||||
}
|
||||
|
||||
$valid_time = (int)$this->getParam('valid_time', true, 10);
|
||||
$allowed_from = $this->getParam('allowed_from', true, '');
|
||||
|
||||
$valid_time = Validate::validate($valid_time, 'valid time', '/^(1[0-1][0-9]|120|[1-9][0-9])$/', 'invalid_validtime', [10], true);
|
||||
|
||||
// validate allowed_from
|
||||
if (!empty($allowed_from)) {
|
||||
$ip_list = array_map('trim', explode(",", $allowed_from));
|
||||
$_check_list = $ip_list;
|
||||
foreach ($_check_list as $idx => $ip) {
|
||||
if (Validate::validate_ip2($ip, true, 'invalidip', true, true, true) == false) {
|
||||
throw new Exception('Invalid ip address', 406);
|
||||
}
|
||||
// check for cidr
|
||||
if (strpos($ip, '/') !== false) {
|
||||
$ipparts = explode("/", $ip);
|
||||
// shorten IP
|
||||
$ip = inet_ntop(inet_pton($ipparts[0]));
|
||||
// re-add cidr
|
||||
$ip .= '/' . $ipparts[1];
|
||||
} else {
|
||||
// shorten IP
|
||||
$ip = inet_ntop(inet_pton($ip));
|
||||
}
|
||||
$ip_list[$idx] = $ip;
|
||||
}
|
||||
$allowed_from = implode(",", array_unique($ip_list));
|
||||
}
|
||||
|
||||
$hash = hash('sha256', openssl_random_pseudo_bytes(64 * 64));
|
||||
|
||||
$ins_stmt = Database::prepare("
|
||||
INSERT INTO `" . TABLE_PANEL_LOGINLINKS . "`
|
||||
SET `hash` = :hash, `loginname` = :loginname, `valid_until` = :validuntil, `allowed_from` = :allowedfrom
|
||||
ON DUPLICATE KEY UPDATE `hash` = :hash, `valid_until` = :validuntil, `allowed_from` = :allowedfrom
|
||||
");
|
||||
Database::pexecute($ins_stmt, [
|
||||
'hash' => $hash,
|
||||
'loginname' => $customer['loginname'],
|
||||
'validuntil' => time() + $valid_time,
|
||||
'allowedfrom' => $allowed_from
|
||||
]);
|
||||
|
||||
return $this->response([
|
||||
'base' => 'https://' . Settings::Get('system.hostname') . '/' . (Settings::Get('system.froxlordirectlyviahostname') != 1 ? basename(\Froxlor\Froxlor::getInstallDir()) . '/' : ''),
|
||||
'uri' => 'index.php?action=ll&ln=' . $customer['loginname'] . '&h=' . $hash
|
||||
]);
|
||||
}
|
||||
throw new Exception("Not allowed to execute given command.", 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* can be used to remotely run the integritiy checks froxlor implements
|
||||
*
|
||||
|
||||
@@ -72,6 +72,8 @@ class Ftps extends ApiCommand implements ResourceEntity
|
||||
* optional whether to add additional usernames to the group
|
||||
* @param bool $is_defaultuser
|
||||
* optional whether this is the standard default ftp user which is being added so no usage is decreased
|
||||
* @param bool $login_enabled
|
||||
* optional whether to allow login (default) or not
|
||||
*
|
||||
* @access admin, customer
|
||||
* @return string json-encoded array
|
||||
@@ -84,6 +86,7 @@ class Ftps extends ApiCommand implements ResourceEntity
|
||||
}
|
||||
|
||||
$is_defaultuser = $this->getBoolParam('is_defaultuser', true, 0);
|
||||
$login_enabled = $this->getBoolParam('login_enabled', true, 1);
|
||||
|
||||
if (($this->getUserDetail('ftps_used') < $this->getUserDetail('ftps') || $this->getUserDetail('ftps') == '-1') || $this->isAdmin() && $is_defaultuser == 1) {
|
||||
// required parameters
|
||||
@@ -176,13 +179,14 @@ class Ftps extends ApiCommand implements ResourceEntity
|
||||
|
||||
$stmt = Database::prepare("INSERT INTO `" . TABLE_FTP_USERS . "`
|
||||
(`customerid`, `username`, `description`, `password`, `homedir`, `login_enabled`, `uid`, `gid`, `shell`)
|
||||
VALUES (:customerid, :username, :description, :password, :homedir, 'y', :guid, :guid, :shell)");
|
||||
VALUES (:customerid, :username, :description, :password, :homedir, :loginenabled, :guid, :guid, :shell)");
|
||||
$params = [
|
||||
"customerid" => $customer['customerid'],
|
||||
"username" => $username,
|
||||
"description" => $description,
|
||||
"password" => $cryptPassword,
|
||||
"homedir" => $path,
|
||||
"loginenabled" => $login_enabled ? 'Y' : 'N',
|
||||
"guid" => $customer['guid'],
|
||||
"shell" => $shell
|
||||
];
|
||||
@@ -389,6 +393,8 @@ class Ftps extends ApiCommand implements ResourceEntity
|
||||
* optional, description for ftp-user
|
||||
* @param string $shell
|
||||
* optional, default /bin/false (not changeable when deactivated)
|
||||
* @param bool $login_enabled
|
||||
* optional whether to allow login (default) or not
|
||||
* @param int $customerid
|
||||
* optional, required when called as admin (if $loginname is not specified)
|
||||
* @param string $loginname
|
||||
@@ -419,6 +425,7 @@ class Ftps extends ApiCommand implements ResourceEntity
|
||||
$password = $this->getParam('ftp_password', true, '');
|
||||
$description = $this->getParam('ftp_description', true, $result['description']);
|
||||
$shell = $this->getParam('shell', true, $result['shell']);
|
||||
$login_enabled = $this->getBoolParam('login_enabled', true, ($result['login_enabled'] == 'Y' ? 1 : 0));
|
||||
|
||||
// validation
|
||||
$password = Validate::validate($password, 'password', '', '', [], true);
|
||||
@@ -430,6 +437,10 @@ class Ftps extends ApiCommand implements ResourceEntity
|
||||
$shell = "/bin/false";
|
||||
}
|
||||
|
||||
if ($login_enabled != 1) {
|
||||
$login_enabled = 0;
|
||||
}
|
||||
|
||||
// get needed customer info to reduce the ftp-user-counter by one
|
||||
$customer = $this->getCustomerData();
|
||||
|
||||
@@ -480,13 +491,14 @@ class Ftps extends ApiCommand implements ResourceEntity
|
||||
|
||||
$stmt = Database::prepare("
|
||||
UPDATE `" . TABLE_FTP_USERS . "`
|
||||
SET `description` = :desc, `shell` = :shell
|
||||
SET `description` = :desc, `shell` = :shell, `login_enabled` = :loginenabled
|
||||
WHERE `customerid` = :customerid
|
||||
AND `id` = :id
|
||||
");
|
||||
Database::pexecute($stmt, [
|
||||
"desc" => $description,
|
||||
"shell" => $shell,
|
||||
"loginenabled" => $login_enabled ? 'Y' : 'N',
|
||||
"customerid" => $customer['customerid'],
|
||||
"id" => $id
|
||||
], true, true);
|
||||
|
||||
@@ -201,7 +201,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
|
||||
|
||||
// validation
|
||||
$name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true);
|
||||
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_CONF_TEXT);
|
||||
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_DESC_TEXT);
|
||||
|
||||
if (Settings::Get('system.mail_quota_enabled') != '1') {
|
||||
$value_arr['email_quota'] = -1;
|
||||
@@ -383,7 +383,7 @@ class HostingPlans extends ApiCommand implements ResourceEntity
|
||||
|
||||
// validation
|
||||
$name = Validate::validate(trim($name), 'name', Validate::REGEX_DESC_TEXT, '', [], true);
|
||||
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_CONF_TEXT);
|
||||
$description = Validate::validate(str_replace("\r\n", "\n", $description), 'description', Validate::REGEX_DESC_TEXT);
|
||||
|
||||
if (Settings::Get('system.mail_quota_enabled') != '1') {
|
||||
$value_arr['email_quota'] = -1;
|
||||
|
||||
@@ -67,6 +67,8 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
* optional, php-settings-id, if empty the $domain value is used
|
||||
* @param int $redirectcode
|
||||
* optional, redirect-code-id from TABLE_PANEL_REDIRECTCODES
|
||||
* @param int $speciallogfile
|
||||
* optional, whether to create an exclusive web-logfile for this domain (1) or not (0) or inherit value from parentdomain (2, default)
|
||||
* @param bool $sslenabled
|
||||
* optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default
|
||||
* 1 (true)
|
||||
@@ -107,6 +109,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
$openbasedir_path = $this->getParam('openbasedir_path', true, 0);
|
||||
$phpsettingid = $this->getParam('phpsettingid', true, 0);
|
||||
$redirectcode = $this->getParam('redirectcode', true, Settings::Get('customredirect.default'));
|
||||
$speciallogfile = intval($this->getParam('speciallogfile', true, 2));
|
||||
$isemaildomain = $this->getParam('isemaildomain', true, 0);
|
||||
if (Settings::Get('system.use_ssl')) {
|
||||
$sslenabled = $this->getBoolParam('sslenabled', true, 1);
|
||||
@@ -229,6 +232,9 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
} elseif ($completedomain_check && strtolower($completedomain_check['domain']) == strtolower($completedomain)) {
|
||||
// the domain does already exist as main-domain
|
||||
Response::standardError('domainexistalready', $completedomain, true);
|
||||
} elseif ((int)$domain_check['deactivated'] == 1) {
|
||||
// main domain is deactivated
|
||||
Response::standardError('maindomaindeactivated', $domain, true);
|
||||
}
|
||||
|
||||
// if allowed, check for 'is email domain'-flag
|
||||
@@ -273,6 +279,11 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
$ssl_redirect = 2;
|
||||
}
|
||||
|
||||
// validate speciallogfile value
|
||||
if ($speciallogfile < 0 || $speciallogfile > 2) {
|
||||
$speciallogfile = 2; // inherit from parent-domain
|
||||
}
|
||||
|
||||
// get the phpsettingid from parentdomain, #107
|
||||
$phpsid_stmt = Database::prepare("
|
||||
SELECT `phpsettingid` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `id` = :id
|
||||
@@ -351,7 +362,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
"openbasedir" => $domain_check['openbasedir'],
|
||||
"openbasedir_path" => $openbasedir_path,
|
||||
"phpenabled" => $domain_check['phpenabled'],
|
||||
"speciallogfile" => $domain_check['speciallogfile'],
|
||||
"speciallogfile" => $speciallogfile == 2 ? $domain_check['speciallogfile'] : $speciallogfile,
|
||||
"specialsettings" => $domain_check['specialsettings'],
|
||||
"ssl_specialsettings" => $domain_check['ssl_specialsettings'],
|
||||
"include_specialsettings" => $domain_check['include_specialsettings'],
|
||||
@@ -588,6 +599,11 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
* optional, php-settings-id, if empty the $domain value is used
|
||||
* @param int $redirectcode
|
||||
* optional, redirect-code-id from TABLE_PANEL_REDIRECTCODES
|
||||
* @param bool $speciallogfile
|
||||
* optional, whether to create an exclusive web-logfile for this domain
|
||||
* @param bool $speciallogverified
|
||||
* optional, when setting $speciallogfile to false, this needs to be set to true to confirm the action,
|
||||
* default 0 (false)
|
||||
* @param bool $sslenabled
|
||||
* optional, whether or not SSL is enabled for this domain, regardless of the assigned ssl-ips, default
|
||||
* 1 (true)
|
||||
@@ -645,6 +661,8 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
$openbasedir_path = $this->getParam('openbasedir_path', true, $result['openbasedir_path']);
|
||||
$phpsettingid = $this->getParam('phpsettingid', true, $result['phpsettingid']);
|
||||
$redirectcode = $this->getParam('redirectcode', true, Domain::getDomainRedirectId($id));
|
||||
$speciallogfile = $this->getBoolParam('speciallogfile', true, $result['speciallogfile']);
|
||||
$speciallogverified = $this->getBoolParam('speciallogverified', true, 0);
|
||||
if (Settings::Get('system.use_ssl')) {
|
||||
$sslenabled = $this->getBoolParam('sslenabled', true, $result['ssl_enabled']);
|
||||
$ssl_redirect = $this->getBoolParam('ssl_redirect', true, $result['ssl_redirect']);
|
||||
@@ -754,6 +772,10 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
$ssl_redirect = 2;
|
||||
}
|
||||
|
||||
if ($speciallogfile != $result['speciallogfile'] && $speciallogverified != '1') {
|
||||
$speciallogfile = $result['speciallogfile'];
|
||||
}
|
||||
|
||||
// is-email-domain flag changed - remove mail accounts and mail-addresses
|
||||
if (($result['isemaildomain'] == '1') && $isemaildomain == '0') {
|
||||
$params = [
|
||||
@@ -786,7 +808,21 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
Domain::updateRedirectOfDomain($id, $redirectcode);
|
||||
}
|
||||
|
||||
if ($path != $result['documentroot'] || $isemaildomain != $result['isemaildomain'] || $wwwserveralias != $result['wwwserveralias'] || $iswildcarddomain != $result['iswildcarddomain'] || $aliasdomain != (int)$result['aliasdomain'] || $openbasedir_path != $result['openbasedir_path'] || $ssl_redirect != $result['ssl_redirect'] || $letsencrypt != $result['letsencrypt'] || $hsts_maxage != $result['hsts'] || $hsts_sub != $result['hsts_sub'] || $hsts_preload != $result['hsts_preload'] || $phpsettingid != $result['phpsettingid'] || $http2 != $result['http2']) {
|
||||
if ($path != $result['documentroot']
|
||||
|| $isemaildomain != $result['isemaildomain']
|
||||
|| $wwwserveralias != $result['wwwserveralias']
|
||||
|| $iswildcarddomain != $result['iswildcarddomain']
|
||||
|| $aliasdomain != (int)$result['aliasdomain']
|
||||
|| $openbasedir_path != $result['openbasedir_path']
|
||||
|| $ssl_redirect != $result['ssl_redirect']
|
||||
|| $letsencrypt != $result['letsencrypt']
|
||||
|| $hsts_maxage != $result['hsts']
|
||||
|| $hsts_sub != $result['hsts_sub']
|
||||
|| $hsts_preload != $result['hsts_preload']
|
||||
|| $phpsettingid != $result['phpsettingid']
|
||||
|| $http2 != $result['http2']
|
||||
|| ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1')
|
||||
) {
|
||||
$stmt = Database::prepare("
|
||||
UPDATE `" . TABLE_PANEL_DOMAINS . "` SET
|
||||
`documentroot` = :documentroot,
|
||||
@@ -802,7 +838,8 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
`hsts` = :hsts,
|
||||
`hsts_sub` = :hsts_sub,
|
||||
`hsts_preload` = :hsts_preload,
|
||||
`phpsettingid` = :phpsettingid
|
||||
`phpsettingid` = :phpsettingid,
|
||||
`speciallogfile` = :speciallogfile
|
||||
WHERE `customerid`= :customerid AND `id`= :id
|
||||
");
|
||||
$params = [
|
||||
@@ -820,6 +857,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
"hsts_sub" => $hsts_sub,
|
||||
"hsts_preload" => $hsts_preload,
|
||||
"phpsettingid" => $phpsettingid,
|
||||
"speciallogfile" => $speciallogfile,
|
||||
"customerid" => $customer['customerid'],
|
||||
"id" => $id
|
||||
];
|
||||
@@ -865,7 +903,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
}
|
||||
|
||||
/**
|
||||
* lists all subdomain entries
|
||||
* lists all customer domain/subdomain entries
|
||||
*
|
||||
* @param bool $with_ips
|
||||
* optional, default true
|
||||
@@ -910,17 +948,12 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
$custom_list_result = $_custom_list_result['list'];
|
||||
}
|
||||
$customer_ids = [];
|
||||
$customer_stdsubs = [];
|
||||
foreach ($custom_list_result as $customer) {
|
||||
$customer_ids[] = $customer['customerid'];
|
||||
$customer_stdsubs[$customer['customerid']] = $customer['standardsubdomain'];
|
||||
}
|
||||
if (empty($customer_ids)) {
|
||||
throw new Exception("Required resource unsatisfied.", 405);
|
||||
}
|
||||
if (empty($customer_stdsubs)) {
|
||||
throw new Exception("Required resource unsatisfied.", 405);
|
||||
}
|
||||
|
||||
$select_fields = [
|
||||
'`d`.*'
|
||||
@@ -932,9 +965,6 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
$customer_ids = [
|
||||
$this->getUserDetail('customerid')
|
||||
];
|
||||
$customer_stdsubs = [
|
||||
$this->getUserDetail('customerid') => $this->getUserDetail('standardsubdomain')
|
||||
];
|
||||
|
||||
$select_fields = [
|
||||
'`d`.`id`',
|
||||
@@ -949,7 +979,8 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
'`d`.`parentdomainid`',
|
||||
'`d`.`letsencrypt`',
|
||||
'`d`.`registration_date`',
|
||||
'`d`.`termination_date`'
|
||||
'`d`.`termination_date`',
|
||||
'`d`.`deactivated`'
|
||||
];
|
||||
}
|
||||
$query_fields = [];
|
||||
@@ -963,7 +994,7 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `pd` ON `pd`.`id`=`d`.`parentdomainid`
|
||||
WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ")
|
||||
AND `d`.`email_only` = '0'
|
||||
AND `d`.`id` NOT IN (" . implode(', ', $customer_stdsubs) . ")" . $this->getSearchWhere($query_fields, true) . " GROUP BY `d`.`id` ORDER BY `parentdomainname` ASC, `d`.`parentdomainid` ASC " . $this->getOrderBy(true) . $this->getLimit());
|
||||
" . $this->getSearchWhere($query_fields, true) . " GROUP BY `d`.`id` ORDER BY `parentdomainname` ASC, `d`.`parentdomainid` ASC " . $this->getOrderBy(true) . $this->getLimit());
|
||||
|
||||
$result = [];
|
||||
Database::pexecute($domains_stmt, $query_fields, true, true);
|
||||
@@ -1063,17 +1094,19 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
||||
$this->getUserDetail('customerid') => $this->getUserDetail('standardsubdomain')
|
||||
];
|
||||
}
|
||||
// prepare select statement
|
||||
$domains_stmt = Database::prepare("
|
||||
SELECT COUNT(*) as num_subdom
|
||||
FROM `" . TABLE_PANEL_DOMAINS . "` `d`
|
||||
WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ")
|
||||
AND `d`.`email_only` = '0'
|
||||
AND `d`.`id` NOT IN (" . implode(', ', $customer_stdsubs) . ")
|
||||
");
|
||||
$result = Database::pexecute_first($domains_stmt, null, true, true);
|
||||
if ($result) {
|
||||
return $this->response($result['num_subdom']);
|
||||
if (!empty($customer_ids)) {
|
||||
// prepare select statement
|
||||
$domains_stmt = Database::prepare("
|
||||
SELECT COUNT(*) as num_subdom
|
||||
FROM `" . TABLE_PANEL_DOMAINS . "` `d`
|
||||
WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ")
|
||||
AND `d`.`email_only` = '0'
|
||||
AND `d`.`id` NOT IN (" . implode(', ', $customer_stdsubs) . ")
|
||||
");
|
||||
$result = Database::pexecute_first($domains_stmt, null, true, true);
|
||||
if ($result) {
|
||||
return $this->response($result['num_subdom']);
|
||||
}
|
||||
}
|
||||
return $this->response(0);
|
||||
}
|
||||
|
||||
@@ -112,11 +112,11 @@ class FroxlorRPC
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function validateAllowedFrom(array $allowed_from, string $remote_addr): bool
|
||||
public static function validateAllowedFrom(array $allowed_from, string $remote_addr): bool
|
||||
{
|
||||
// shorten IP for comparison
|
||||
$remote_addr = inet_ntop(inet_pton($remote_addr));
|
||||
// check for diret matches
|
||||
// check for direct matches
|
||||
if (in_array($remote_addr, $allowed_from)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -25,19 +25,18 @@
|
||||
|
||||
namespace Froxlor\Cli;
|
||||
|
||||
use PDO;
|
||||
use Exception;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\Settings;
|
||||
use Froxlor\Database\Database;
|
||||
use PDO;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CliCommand extends Command
|
||||
{
|
||||
|
||||
protected function validateRequirements(InputInterface $input, OutputInterface $output, bool $ignore_has_updates = false): int
|
||||
protected function validateRequirements(OutputInterface $output, bool $ignore_has_updates = false): int
|
||||
{
|
||||
if (!file_exists(Froxlor::getInstallDir() . '/lib/userdata.inc.php')) {
|
||||
$output->writeln("<error>Could not find froxlor's userdata.inc.php file. You should use this script only with an installed froxlor system.</>");
|
||||
@@ -116,9 +115,11 @@ class CliCommand extends Command
|
||||
return $userinfo;
|
||||
}
|
||||
|
||||
private function runUpdate(OutputInterface $output): int
|
||||
protected function runUpdate(OutputInterface $output, bool $manual = false): int
|
||||
{
|
||||
$output->writeln('<comment>Automatic update is activated and we are going to proceed without any notices</>');
|
||||
if (!$manual) {
|
||||
$output->writeln('<comment>Automatic update is activated and we are going to proceed without any notices</>');
|
||||
}
|
||||
include_once Froxlor::getInstallDir() . '/lib/tables.inc.php';
|
||||
define('_CRON_UPDATE', 1);
|
||||
ob_start([
|
||||
@@ -127,11 +128,11 @@ class CliCommand extends Command
|
||||
]);
|
||||
include_once Froxlor::getInstallDir() . '/install/updatesql.php';
|
||||
ob_end_flush();
|
||||
$output->writeln('<info>Automatic update done - you should check your settings to be sure everything is fine</>');
|
||||
$output->writeln('<info>' . ($manual ? 'Database' : 'Automatic') . ' update done - you should check your settings to be sure everything is fine</>');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function cleanUpdateOutput($buffer)
|
||||
private function cleanUpdateOutput($buffer): string
|
||||
{
|
||||
return strip_tags(preg_replace("/<br\W*?\/>/", "\n", $buffer));
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ final class ConfigDiff extends CliCommand
|
||||
->addOption('diff-params', '', InputOption::VALUE_REQUIRED, 'Additional parameters for `diff`, e.g. --diff-params="--color=always"');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
require Froxlor::getInstallDir() . '/lib/functions.php';
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
namespace Froxlor\Cli;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Config\ConfigParser;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\FileDir;
|
||||
@@ -40,9 +41,8 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
final class ConfigServices extends CliCommand
|
||||
{
|
||||
|
||||
private $yes_to_all_supported = [
|
||||
/* 'bookworm', */
|
||||
'bookworm',
|
||||
'bionic',
|
||||
'bullseye',
|
||||
'buster',
|
||||
@@ -62,11 +62,9 @@ final class ConfigServices extends CliCommand
|
||||
->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Install packages without asking questions (Debian/Ubuntu only currently)');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$result = self::SUCCESS;
|
||||
|
||||
$result = $this->validateRequirements($input, $output);
|
||||
$result = $this->validateRequirements($output);
|
||||
|
||||
require Froxlor::getInstallDir() . '/lib/functions.php';
|
||||
|
||||
@@ -93,7 +91,7 @@ final class ConfigServices extends CliCommand
|
||||
if ($result == self::SUCCESS) {
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
if ($input->getOption('create')) {
|
||||
$result = $this->createConfig($input, $output, $io);
|
||||
$result = $this->createConfig($output, $io);
|
||||
} elseif ($input->getOption('apply')) {
|
||||
$result = $this->applyConfig($input, $output, $io);
|
||||
} elseif ($input->getOption('list') || $input->getOption('daemon')) {
|
||||
@@ -158,7 +156,10 @@ final class ConfigServices extends CliCommand
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
private function createConfig(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function createConfig(OutputInterface $output, SymfonyStyle $io): int
|
||||
{
|
||||
$_daemons_config = [
|
||||
'distro' => ""
|
||||
@@ -285,7 +286,10 @@ final class ConfigServices extends CliCommand
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function applyConfig(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function applyConfig(InputInterface $input, OutputInterface $output, SymfonyStyle $io): int
|
||||
{
|
||||
$applyFile = $input->getOption('apply');
|
||||
|
||||
@@ -429,7 +433,10 @@ final class ConfigServices extends CliCommand
|
||||
}
|
||||
}
|
||||
|
||||
private function getReplacerArray()
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getReplacerArray(): array
|
||||
{
|
||||
$customer_tmpdir = '/tmp/';
|
||||
if (Settings::Get('system.mod_fcgid') == '1' && Settings::Get('system.mod_fcgid_tmpdir') != '') {
|
||||
@@ -438,7 +445,7 @@ final class ConfigServices extends CliCommand
|
||||
$customer_tmpdir = Settings::Get('phpfpm.tmpdir');
|
||||
}
|
||||
|
||||
// try to convert namserver hosts to ip's
|
||||
// try to convert nameserver hosts to ip's
|
||||
$ns_ips = "";
|
||||
$known_ns_ips = [];
|
||||
if (Settings::Get('system.nameservers') != '') {
|
||||
@@ -484,12 +491,12 @@ final class ConfigServices extends CliCommand
|
||||
Database::needSqlData();
|
||||
$sql = Database::getSqlData();
|
||||
|
||||
$replace_arr = [
|
||||
return [
|
||||
'<SQL_UNPRIVILEGED_USER>' => $sql['user'],
|
||||
'<SQL_UNPRIVILEGED_PASSWORD>' => $sql['passwd'],
|
||||
'<SQL_DB>' => $sql['db'],
|
||||
'<SQL_HOST>' => $sql['host'],
|
||||
'<SQL_SOCKET>' => isset($sql['socket']) ? $sql['socket'] : null,
|
||||
'<SQL_SOCKET>' => $sql['socket'] ?? null,
|
||||
'<SERVERNAME>' => Settings::Get('system.hostname'),
|
||||
'<SERVERIP>' => Settings::Get('system.ipaddress'),
|
||||
'<NAMESERVERS>' => Settings::Get('system.nameservers'),
|
||||
@@ -508,6 +515,5 @@ final class ConfigServices extends CliCommand
|
||||
'<SSL_CERT_FILE>' => Settings::Get('system.ssl_cert_file'),
|
||||
'<SSL_KEY_FILE>' => Settings::Get('system.ssl_key_file'),
|
||||
];
|
||||
return $replace_arr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +26,13 @@
|
||||
namespace Froxlor\Cli;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\Config\ConfigParser;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\Install\Install;
|
||||
use Froxlor\Install\Install\Core;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
@@ -53,7 +53,10 @@ final class InstallCommand extends Command
|
||||
->addOption('create-userdata-from-str', 'c', InputOption::VALUE_REQUIRED, 'Creates lib/userdata.inc.php file from string created by web-install process');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$result = self::SUCCESS;
|
||||
|
||||
@@ -137,10 +140,12 @@ final class InstallCommand extends Command
|
||||
$decoded_input = [];
|
||||
}
|
||||
|
||||
$result = $this->showStep(0, $extended, $decoded_input);
|
||||
return $result;
|
||||
return $this->showStep(0, $extended, $decoded_input);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function showStep(int $step = 0, bool $extended = false, array $decoded_input = []): int
|
||||
{
|
||||
$result = self::SUCCESS;
|
||||
|
||||
@@ -25,19 +25,20 @@
|
||||
|
||||
namespace Froxlor\Cli;
|
||||
|
||||
use PDO;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\FileDir;
|
||||
use Froxlor\Settings;
|
||||
use Froxlor\FroxlorLogger;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\System\Cronjob;
|
||||
use Froxlor\Cron\TaskId;
|
||||
use Exception;
|
||||
use Froxlor\Cron\CronConfig;
|
||||
use Froxlor\Cron\System\Extrausers;
|
||||
use Froxlor\Cron\TaskId;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\FileDir;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\FroxlorLogger;
|
||||
use Froxlor\Settings;
|
||||
use Froxlor\System\Cronjob;
|
||||
use PDO;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
final class MasterCron extends CliCommand
|
||||
@@ -52,15 +53,17 @@ final class MasterCron extends CliCommand
|
||||
$this->setDescription('Regulary perform tasks created by froxlor');
|
||||
$this->addArgument('job', InputArgument::IS_ARRAY, 'Job(s) to run');
|
||||
$this->addOption('run-task', 'r', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Run a specific task [1 = re-generate configs, 4 = re-generate dns zones, 10 = re-set quotas, 99 = re-create cron.d-file]')
|
||||
->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces re-generating of config-files (webserver, nameserver, etc.)')
|
||||
->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces given job or, if none given, forces re-generating of config-files (webserver, nameserver, etc.)')
|
||||
->addOption('debug', 'd', InputOption::VALUE_NONE, 'Output debug information about what is going on to STDOUT.')
|
||||
->addOption('no-fork', 'N', InputOption::VALUE_NONE, 'Do not fork to background (traffic cron only).');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$result = self::SUCCESS;
|
||||
$result = $this->validateRequirements($input, $output);
|
||||
$result = $this->validateRequirements($output);
|
||||
|
||||
if ($result != self::SUCCESS) {
|
||||
// requirements failed, exit
|
||||
@@ -71,12 +74,13 @@ final class MasterCron extends CliCommand
|
||||
|
||||
// handle force option
|
||||
if ($input->getOption('force')) {
|
||||
// rebuild all config files
|
||||
Cronjob::inserttask(TaskId::REBUILD_VHOST);
|
||||
Cronjob::inserttask(TaskId::REBUILD_DNS);
|
||||
Cronjob::inserttask(TaskId::CREATE_QUOTA);
|
||||
Cronjob::inserttask(TaskId::REBUILD_CRON);
|
||||
array_push($jobs, 'tasks');
|
||||
if (empty($jobs) || in_array('tasks', $jobs)) {
|
||||
Cronjob::inserttask(TaskId::REBUILD_VHOST);
|
||||
Cronjob::inserttask(TaskId::REBUILD_DNS);
|
||||
Cronjob::inserttask(TaskId::CREATE_QUOTA);
|
||||
Cronjob::inserttask(TaskId::REBUILD_CRON);
|
||||
$jobs[] = 'tasks';
|
||||
}
|
||||
define('CRON_IS_FORCED', 1);
|
||||
}
|
||||
// handle debug option
|
||||
@@ -91,9 +95,9 @@ final class MasterCron extends CliCommand
|
||||
if ($input->getOption('run-task')) {
|
||||
$tasks_to_run = $input->getOption('run-task');
|
||||
foreach ($tasks_to_run as $ttr) {
|
||||
if (in_array($ttr, [1, 4, 10, 99])) {
|
||||
if (in_array($ttr, [TaskId::REBUILD_VHOST, TaskId::REBUILD_DNS, TaskId::CREATE_QUOTA, TaskId::REBUILD_CRON])) {
|
||||
Cronjob::inserttask($ttr);
|
||||
array_push($jobs, 'tasks');
|
||||
$jobs[] = 'tasks';
|
||||
} else {
|
||||
$output->writeln('<comment>Unknown task number "' . $ttr . '"</>');
|
||||
}
|
||||
@@ -139,12 +143,12 @@ final class MasterCron extends CliCommand
|
||||
$cronfile::run();
|
||||
}
|
||||
// free the lockfile
|
||||
$this->unlockJob($job);
|
||||
$this->unlockJob();
|
||||
}
|
||||
}
|
||||
|
||||
// regenerate nss-extrausers files / invalidate nscd cache (if used)
|
||||
$this->refreshUsers((int) $tasks_cnt['jobcnt']);
|
||||
$this->refreshUsers((int)$tasks_cnt['jobcnt']);
|
||||
|
||||
// we have to check the system's last guid with every cron run
|
||||
// in case the admin installed new software which added a new user
|
||||
@@ -156,40 +160,25 @@ final class MasterCron extends CliCommand
|
||||
CronConfig::checkCrondConfigurationFile();
|
||||
|
||||
// check for old/compatibility cronjob file
|
||||
if (file_exists(Froxlor::getInstallDir().'/scripts/froxlor_master_cronjob.php')) {
|
||||
@unlink(Froxlor::getInstallDir().'/scripts/froxlor_master_cronjob.php');
|
||||
@rmdir(Froxlor::getInstallDir().'/scripts');
|
||||
if (file_exists(Froxlor::getInstallDir() . '/scripts/froxlor_master_cronjob.php')) {
|
||||
@unlink(Froxlor::getInstallDir() . '/scripts/froxlor_master_cronjob.php');
|
||||
@rmdir(Froxlor::getInstallDir() . '/scripts');
|
||||
}
|
||||
|
||||
// reset cronlog-flag if set to "once"
|
||||
if ((int) Settings::Get('logger.log_cron') == 1) {
|
||||
if ((int)Settings::Get('logger.log_cron') == 1) {
|
||||
FroxlorLogger::getInstanceOf()->setCronLog(0);
|
||||
}
|
||||
|
||||
// clean up possible old login-links
|
||||
Database::query("DELETE FROM `" . TABLE_PANEL_LOGINLINKS . "` WHERE `valid_until` < UNIX_TIMESTAMP()");
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function refreshUsers(int $jobcount = 0)
|
||||
{
|
||||
if ($jobcount > 0) {
|
||||
if (Settings::Get('system.nssextrausers') == 1) {
|
||||
Extrausers::generateFiles($this->cronLog);
|
||||
return;
|
||||
}
|
||||
|
||||
// clear NSCD cache if using fcgid or fpm, #1570 - not needed for nss-extrausers
|
||||
if ((Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) && Settings::Get('system.nssextrausers') == 0) {
|
||||
$false_val = false;
|
||||
FileDir::safe_exec('nscd -i passwd 1> /dev/null', $false_val, [
|
||||
'>'
|
||||
]);
|
||||
FileDir::safe_exec('nscd -i group 1> /dev/null', $false_val, [
|
||||
'>'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function validateOwnership(OutputInterface $output)
|
||||
{
|
||||
// when using fcgid or fpm for froxlor-vhost itself, we have to check
|
||||
@@ -216,21 +205,6 @@ final class MasterCron extends CliCommand
|
||||
$output->writeln('OK');
|
||||
}
|
||||
|
||||
private function getCronModule(string $cronname, OutputInterface $output)
|
||||
{
|
||||
$upd_stmt = Database::prepare("
|
||||
SELECT `cronclass` FROM `" . TABLE_PANEL_CRONRUNS . "` WHERE `cronfile` = :cron;
|
||||
");
|
||||
$cron = Database::pexecute_first($upd_stmt, [
|
||||
'cron' => $cronname
|
||||
]);
|
||||
if ($cron) {
|
||||
return $cron['cronclass'];
|
||||
}
|
||||
$output->writeln("<error>Requested cronjob '" . $cronname . "' could not be found.</>");
|
||||
return false;
|
||||
}
|
||||
|
||||
private function lockJob(string $job, OutputInterface $output): bool
|
||||
{
|
||||
|
||||
@@ -243,12 +217,12 @@ final class MasterCron extends CliCommand
|
||||
system("kill -CHLD " . (int)$jobinfo['pid'] . " 1> /dev/null 2> /dev/null", $check_pid_return);
|
||||
if ($check_pid_return == 1) {
|
||||
// Process does not seem to run, most likely it has died
|
||||
$this->unlockJob($job);
|
||||
$this->unlockJob();
|
||||
} else {
|
||||
// cronjob still running, output info and stop
|
||||
$output->writeln([
|
||||
'<comment>Job "' . $jobinfo['job'] . '" is currently running.',
|
||||
'Started: ' . date('d.m.Y H:i', (int) $jobinfo['startts']),
|
||||
'Started: ' . date('d.m.Y H:i', (int)$jobinfo['startts']),
|
||||
'PID: ' . $jobinfo['pid'] . '</>'
|
||||
]);
|
||||
return false;
|
||||
@@ -264,8 +238,44 @@ final class MasterCron extends CliCommand
|
||||
return true;
|
||||
}
|
||||
|
||||
private function unlockJob(string $job): bool
|
||||
private function unlockJob(): bool
|
||||
{
|
||||
return @unlink($this->lockFile);
|
||||
}
|
||||
|
||||
private function getCronModule(string $cronname, OutputInterface $output)
|
||||
{
|
||||
$upd_stmt = Database::prepare("
|
||||
SELECT `cronclass` FROM `" . TABLE_PANEL_CRONRUNS . "` WHERE `cronfile` = :cron;
|
||||
");
|
||||
$cron = Database::pexecute_first($upd_stmt, [
|
||||
'cron' => $cronname
|
||||
]);
|
||||
if ($cron) {
|
||||
return $cron['cronclass'];
|
||||
}
|
||||
$output->writeln("<error>Requested cronjob '" . $cronname . "' could not be found.</>");
|
||||
return false;
|
||||
}
|
||||
|
||||
private function refreshUsers(int $jobcount = 0)
|
||||
{
|
||||
if ($jobcount > 0) {
|
||||
if (Settings::Get('system.nssextrausers') == 1) {
|
||||
Extrausers::generateFiles($this->cronLog);
|
||||
return;
|
||||
}
|
||||
|
||||
// clear NSCD cache if using fcgid or fpm, #1570 - not needed for nss-extrausers
|
||||
if ((Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) && Settings::Get('system.nssextrausers') == 0) {
|
||||
$false_val = false;
|
||||
FileDir::safe_exec('nscd -i passwd 1> /dev/null', $false_val, [
|
||||
'>'
|
||||
]);
|
||||
FileDir::safe_exec('nscd -i group 1> /dev/null', $false_val, [
|
||||
'>'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,9 +43,9 @@ final class PhpSessionclean extends CliCommand
|
||||
$this->addArgument('max-lifetime', InputArgument::OPTIONAL, 'The number of seconds after which data will be seen as "garbage" and potentially cleaned up. Defaults to "1440"');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$result = $this->validateRequirements($input, $output);
|
||||
$result = $this->validateRequirements($output);
|
||||
|
||||
if ($result == self::SUCCESS) {
|
||||
if ((int)Settings::Get('phpfpm.enabled') == 1) {
|
||||
@@ -89,7 +89,7 @@ final class PhpSessionclean extends CliCommand
|
||||
|
||||
if (count($paths_to_clean) > 0) {
|
||||
foreach ($paths_to_clean as $ptc) {
|
||||
// find all files older then maxlifetime and delete them
|
||||
// find all files older than maxlifetime and delete them
|
||||
FileDir::safe_exec("find -O3 \"" . $ptc . "\" -ignore_readdir_race -depth -mindepth 1 -name 'sess_*' -type f -cmin \"+" . $maxlifetime . "\" -delete");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,14 +26,12 @@
|
||||
namespace Froxlor\Cli;
|
||||
|
||||
use Exception;
|
||||
use PDO;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Froxlor\Froxlor;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\Froxlor;
|
||||
|
||||
final class RunApiCommand extends CliCommand
|
||||
{
|
||||
@@ -48,11 +46,9 @@ final class RunApiCommand extends CliCommand
|
||||
$this->addOption('show-params', 's', InputOption::VALUE_NONE, 'Show possible parameters for given api-command (given command will *not* be called)');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$result = self::SUCCESS;
|
||||
|
||||
$result = $this->validateRequirements($input, $output);
|
||||
$result = $this->validateRequirements($output);
|
||||
|
||||
require Froxlor::getInstallDir() . '/lib/functions.php';
|
||||
|
||||
@@ -110,6 +106,9 @@ final class RunApiCommand extends CliCommand
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function validateCommand(string $command): array
|
||||
{
|
||||
$command = explode(".", $command);
|
||||
|
||||
@@ -43,11 +43,9 @@ final class SwitchServerIp extends CliCommand
|
||||
->addOption('list', 'l', InputOption::VALUE_NONE, 'List all IP addresses currently added for this server in froxlor');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$result = self::SUCCESS;
|
||||
|
||||
$result = $this->validateRequirements($input, $output);
|
||||
$result = $this->validateRequirements($output);
|
||||
|
||||
if ($result == self::SUCCESS && $input->getOption('list') == false && $input->getOption('switch') == false) {
|
||||
$output->writeln('<error>Either --list or --switch option must be provided. Nothing to do, exiting.</>');
|
||||
@@ -83,6 +81,7 @@ final class SwitchServerIp extends CliCommand
|
||||
$ip_list = $input->getOption('switch');
|
||||
|
||||
$has_error = false;
|
||||
$ips_to_switch = [];
|
||||
foreach ($ip_list as $ips_combo) {
|
||||
$ip_pair = explode(",", $ips_combo);
|
||||
if (count($ip_pair) != 2) {
|
||||
|
||||
@@ -27,9 +27,9 @@ namespace Froxlor\Cli;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\Settings;
|
||||
use Froxlor\Install\Update;
|
||||
use Froxlor\Install\AutoUpdate;
|
||||
use Froxlor\Install\Update;
|
||||
use Froxlor\Settings;
|
||||
use Froxlor\System\Mailer;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
@@ -44,6 +44,7 @@ final class UpdateCommand extends CliCommand
|
||||
$this->setName('froxlor:update');
|
||||
$this->setDescription('Check for newer version and update froxlor');
|
||||
$this->addOption('check-only', 'c', InputOption::VALUE_NONE, 'Only check for newer version and exit')
|
||||
->addOption('database', 'd', InputOption::VALUE_NONE, 'Only run database updates in case updates are done via apt or manually.')
|
||||
->addOption('mail-notify', 'm', InputOption::VALUE_NONE, 'Additionally inform administrator via email if a newer version was found')
|
||||
->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for download, extract and database-update, just do it (if not --check-only is set)')
|
||||
->addOption('integer-return', 'i', InputOption::VALUE_NONE, 'Return integer whether a new version is available or not (implies --check-only). Useful for programmatic use.');
|
||||
@@ -53,8 +54,36 @@ final class UpdateCommand extends CliCommand
|
||||
{
|
||||
$result = self::SUCCESS;
|
||||
|
||||
// database update only
|
||||
if ($input->getOption('database')) {
|
||||
$result = $this->validateRequirements($input, $output, true);
|
||||
if ($result == self::SUCCESS) {
|
||||
if (Froxlor::hasUpdates() || Froxlor::hasDbUpdates()) {
|
||||
$output->writeln('<info>' . lng('updates.dbupdate_required') . '</>');
|
||||
if ($input->getOption('check-only')) {
|
||||
$output->writeln('<comment>Doing nothing because of "check-only" flag.</>');
|
||||
} else {
|
||||
$yestoall = $input->getOption('yes-to-all') !== false;
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new ConfirmationQuestion('Update database? [no] ', false, '/^(y|j)/i');
|
||||
if ($yestoall || $helper->ask($input, $output, $question)) {
|
||||
$result = $this->runUpdate($output, true);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
$output->writeln('<info>' . lng('update.noupdatesavail', (Settings::Get('system.update_channel') == 'testing' ? lng('serversettings.uc_testing') . ' ' : '')) . '</>');
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result = $this->validateRequirements($input, $output);
|
||||
|
||||
if ($result != self::SUCCESS) {
|
||||
// requirements failed, exit
|
||||
return $result;
|
||||
}
|
||||
|
||||
require Froxlor::getInstallDir() . '/lib/functions.php';
|
||||
|
||||
// version check
|
||||
@@ -182,22 +211,4 @@ final class UpdateCommand extends CliCommand
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function updateDatabase()
|
||||
{
|
||||
include_once Froxlor::getInstallDir() . '/lib/tables.inc.php';
|
||||
define('_CRON_UPDATE', 1);
|
||||
ob_start([
|
||||
$this,
|
||||
'cleanUpdateOutput'
|
||||
]);
|
||||
include_once Froxlor::getInstallDir() . '/install/updatesql.php';
|
||||
ob_end_flush();
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function cleanUpdateOutput($buffer)
|
||||
{
|
||||
return strip_tags(preg_replace("/<br\W*?\/>/", "\n", $buffer));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,15 +26,15 @@
|
||||
namespace Froxlor\Cli;
|
||||
|
||||
use Exception;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Froxlor\Api\Commands\Admins;
|
||||
use Froxlor\Api\Commands\Customers;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\System\Crypt;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Froxlor\Api\Commands\Admins;
|
||||
use Froxlor\Api\Commands\Customers;
|
||||
use Froxlor\System\Crypt;
|
||||
use Froxlor\Froxlor;
|
||||
|
||||
final class UserCommand extends CliCommand
|
||||
{
|
||||
@@ -50,11 +50,11 @@ final class UserCommand extends CliCommand
|
||||
->addOption('show-info', 's', InputOption::VALUE_NONE, 'Output information details of given user');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$result = self::SUCCESS;
|
||||
|
||||
$result = $this->validateRequirements($input, $output);
|
||||
$result = $this->validateRequirements($output);
|
||||
|
||||
require Froxlor::getInstallDir() . '/lib/functions.php';
|
||||
|
||||
|
||||
@@ -48,15 +48,16 @@ final class ValidateAcmeWebroot extends CliCommand
|
||||
$this->addOption('yes-to-all', 'A', InputOption::VALUE_NONE, 'Do not ask for confirmation, update files if necessary');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$result = self::SUCCESS;
|
||||
|
||||
$result = $this->validateRequirements($input, $output, true);
|
||||
$result = $this->validateRequirements($output, true);
|
||||
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
if ((int) Settings::Get('system.leenabled') == 0) {
|
||||
if ((int)Settings::Get('system.leenabled') == 0) {
|
||||
$io->info("Let's Encrypt not activated in froxlor settings.");
|
||||
$result = self::INVALID;
|
||||
}
|
||||
@@ -94,7 +95,7 @@ final class ValidateAcmeWebroot extends CliCommand
|
||||
$acmesh_challenge_dir = $recommended;
|
||||
// need to update the corresponding acme-alias config-file
|
||||
$acme_alias_file = Settings::Get('system.letsencryptacmeconf');
|
||||
$sed_params = "s@".$former_value."@" . $acmesh_challenge_dir . "@";
|
||||
$sed_params = "s@" . $former_value . "@" . $acmesh_challenge_dir . "@";
|
||||
FileDir::safe_exec('sed -i -e "' . $sed_params . '" ' . escapeshellarg($acme_alias_file));
|
||||
$count_changes++;
|
||||
}
|
||||
@@ -138,8 +139,6 @@ final class ValidateAcmeWebroot extends CliCommand
|
||||
$io->info("Domain '" . $domain . "' Le_Webroot value is correct");
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,8 +62,8 @@ class Bind extends DnsBase
|
||||
$this->bindconf_file = '# ' . Settings::Get('system.bindconf_directory') . 'froxlor_bind.conf' . "\n" . '# Created ' . date('d.m.Y H:i') . "\n" . '# Do NOT manually edit this file, all changes will be deleted after the next domain change at the panel.' . "\n\n";
|
||||
|
||||
foreach ($domains as $domain) {
|
||||
if ($domain['ismainbutsubto'] > 0) {
|
||||
// domains with ismainbutsubto>0 are handled by recursion within walkDomainList()
|
||||
if ($domain['is_child']) {
|
||||
// domains that are subdomains to other main domains are handled by recursion within walkDomainList()
|
||||
continue;
|
||||
}
|
||||
$this->walkDomainList($domain, $domains);
|
||||
@@ -114,7 +114,7 @@ class Bind extends DnsBase
|
||||
$isFroxlorHostname = true;
|
||||
}
|
||||
|
||||
if ($domain['ismainbutsubto'] == 0) {
|
||||
if (!$domain['is_child']) {
|
||||
$zoneContent = (string)Dns::createDomainZone(($domain['id'] == 'none') ? $domain : $domain['id'], $isFroxlorHostname);
|
||||
$domain['zonefile'] = 'domains/' . $domain['domain'] . '.zone';
|
||||
$zonefile_name = FileDir::makeCorrectFile(Settings::Get('system.bindconf_directory') . '/' . $domain['zonefile']);
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
namespace Froxlor\Cron\Dns;
|
||||
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\Domain\Domain;
|
||||
use Froxlor\FileDir;
|
||||
use Froxlor\FroxlorLogger;
|
||||
use Froxlor\PhpHelper;
|
||||
@@ -210,7 +211,6 @@ abstract class DnsBase
|
||||
`d`.`dkim`,
|
||||
`d`.`dkim_id`,
|
||||
`d`.`dkim_pubkey`,
|
||||
`d`.`ismainbutsubto`,
|
||||
`c`.`loginname`,
|
||||
`c`.`guid`
|
||||
FROM
|
||||
@@ -219,7 +219,7 @@ abstract class DnsBase
|
||||
WHERE
|
||||
`d`.`isbinddomain` = '1'
|
||||
ORDER BY
|
||||
`d`.`domain` ASC
|
||||
LENGTH(`d`.`domain`), `d`.`domain` ASC
|
||||
");
|
||||
|
||||
$domains = [];
|
||||
@@ -241,7 +241,6 @@ abstract class DnsBase
|
||||
'bindserial' => date('Ymd') . '00',
|
||||
'dkim' => '0',
|
||||
'iswildcarddomain' => '1',
|
||||
'ismainbutsubto' => '0',
|
||||
'zonefile' => '',
|
||||
'froxlorhost' => '1'
|
||||
];
|
||||
@@ -257,18 +256,23 @@ abstract class DnsBase
|
||||
if (!isset($domains[$key]['children'])) {
|
||||
$domains[$key]['children'] = [];
|
||||
}
|
||||
if ($domains[$key]['ismainbutsubto'] > 0) {
|
||||
if (isset($domains[$domains[$key]['ismainbutsubto']])) {
|
||||
$domains[$domains[$key]['ismainbutsubto']]['children'][] = $domains[$key]['id'];
|
||||
} else {
|
||||
$domains[$key]['ismainbutsubto'] = 0;
|
||||
if (!isset($domains[$key]['is_child'])) {
|
||||
$domains[$key]['is_child'] = false;
|
||||
}
|
||||
$children = Domain::getMainSubdomainIds($key);
|
||||
if (count($children) > 0) {
|
||||
foreach ($children as $child) {
|
||||
if (isset($domains[$child])) {
|
||||
$domains[$key]['children'][] = $domains[$child]['id'];
|
||||
$domains[$child]['is_child'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, str_pad('domId', 9, ' ') . str_pad('domain', 40, ' ') . 'ismainbutsubto ' . str_pad('parent domain', 40, ' ') . "list of child domain ids");
|
||||
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, str_pad('domId', 9, ' ') . str_pad('domain', 40, ' ') . "list of child domain ids");
|
||||
foreach ($domains as $domain) {
|
||||
$logLine = str_pad($domain['id'], 9, ' ') . str_pad($domain['domain'], 40, ' ') . str_pad($domain['ismainbutsubto'], 15, ' ') . str_pad(((isset($domains[$domain['ismainbutsubto']])) ? $domains[$domain['ismainbutsubto']]['domain'] : '-'), 40, ' ') . join(', ', $domain['children']);
|
||||
$logLine = str_pad($domain['id'], 9, ' ') . str_pad($domain['domain'], 40, ' ') . join(', ', $domain['children']);
|
||||
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, $logLine);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,8 +50,8 @@ class PowerDNS extends DnsBase
|
||||
}
|
||||
|
||||
foreach ($domains as $domain) {
|
||||
if ($domain['ismainbutsubto'] > 0) {
|
||||
// domains with ismainbutsubto>0 are handled by recursion within walkDomainList()
|
||||
if ($domain['is_child']) {
|
||||
// domains that are subdomains to other main domains are handled by recursion within walkDomainList()
|
||||
continue;
|
||||
}
|
||||
$this->walkDomainList($domain, $domains);
|
||||
@@ -108,7 +108,7 @@ class PowerDNS extends DnsBase
|
||||
$isFroxlorHostname = true;
|
||||
}
|
||||
|
||||
if ($domain['ismainbutsubto'] == 0) {
|
||||
if (!$domain['is_child']) {
|
||||
$zoneContent = Dns::createDomainZone(($domain['id'] == 'none') ? $domain : $domain['id'], $isFroxlorHostname);
|
||||
if (count($subzones)) {
|
||||
foreach ($subzones as $subzone) {
|
||||
|
||||
57
lib/Froxlor/Cron/Forkable.php
Normal file
57
lib/Froxlor/Cron/Forkable.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Froxlor\Cron;
|
||||
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\FroxlorLogger;
|
||||
|
||||
trait Forkable
|
||||
{
|
||||
public static function runFork($closure, array $attributes = [], int $concurrentChildren = 3)
|
||||
{
|
||||
$childrenPids = [];
|
||||
|
||||
// We only fork if pcntl_fork is available and nofork flag is not set
|
||||
if (function_exists('pcntl_fork') && !defined('CRON_NOFORK_FLAG')) {
|
||||
foreach ($attributes as $closureAttributes) {
|
||||
// We close the database - connection before we fork, so we don't share resources with the child
|
||||
Database::needRoot(false); // this forces the connection to be set to null
|
||||
$pid = pcntl_fork();
|
||||
|
||||
if ($pid == -1) {
|
||||
exit("Error forking...\n");
|
||||
} elseif ($pid == 0) {
|
||||
// re-create db
|
||||
Database::needRoot(false);
|
||||
$closure($closureAttributes);
|
||||
exit();
|
||||
} else {
|
||||
$childrenPids[] = $pid;
|
||||
while (count($childrenPids) >= $concurrentChildren) {
|
||||
foreach ($childrenPids as $key => $pid) {
|
||||
$res = pcntl_waitpid($pid, $status, WNOHANG);
|
||||
// If the process has already exited
|
||||
if ($res == -1 || $res > 0) {
|
||||
unset($childrenPids[$key]);
|
||||
}
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (pcntl_waitpid(0, $status) != -1);
|
||||
} else {
|
||||
if (!defined('CRON_NOFORK_FLAG')) {
|
||||
if (extension_loaded('pcntl')) {
|
||||
$msg = "PHP compiled with pcntl but pcntl_fork function is not available.";
|
||||
} else {
|
||||
$msg = "PHP compiled without pcntl.";
|
||||
}
|
||||
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, $msg . " Not forking " . self::class . ", this may take a long time!");
|
||||
}
|
||||
foreach ($attributes as $closureAttributes) {
|
||||
$closure($closureAttributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -630,29 +630,6 @@ class Apache extends HttpConfigBase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filename for the virtualhost
|
||||
*/
|
||||
protected function getVhostFilename($domain, $ssl_vhost = false)
|
||||
{
|
||||
if ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && ((int)$domain['ismainbutsubto'] == 0 || Domain::domainMainToSubExists($domain['ismainbutsubto']) == false)) {
|
||||
$vhost_no = '35';
|
||||
} elseif ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && (int)$domain['ismainbutsubto'] > 0) {
|
||||
$vhost_no = '30';
|
||||
} else {
|
||||
// number of dots in a domain specifies it's position (and depth of subdomain) starting at 29 going downwards on higher depth
|
||||
$vhost_no = (string)(30 - substr_count($domain['domain'], ".") + 1);
|
||||
}
|
||||
|
||||
if ($ssl_vhost === true) {
|
||||
$vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $vhost_no . '_froxlor_ssl_vhost_' . $domain['domain'] . '.conf');
|
||||
} else {
|
||||
$vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $vhost_no . '_froxlor_normal_vhost_' . $domain['domain'] . '.conf');
|
||||
}
|
||||
|
||||
return $vhost_filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* We compose the virtualhost entry for one domain
|
||||
*/
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace Froxlor\Cron\Http;
|
||||
use Froxlor\Cron\Http\LetsEncrypt\AcmeSh;
|
||||
use Froxlor\Cron\Http\Php\Fpm;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\Domain\Domain;
|
||||
use Froxlor\FileDir;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\FroxlorLogger;
|
||||
@@ -187,4 +188,18 @@ class HttpConfigBase
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filename for the virtualhost
|
||||
*/
|
||||
protected function getVhostFilename(array $domain, bool $ssl_vhost = false, bool $filename_only = false)
|
||||
{
|
||||
// number of dots in a domain specifies its position (and depth of subdomain) starting at 35 going downwards on higher depth
|
||||
$vhost_no = (string)(35 - substr_count($domain['domain'], ".") + 1);
|
||||
$filename = $vhost_no . '_froxlor_' . ($ssl_vhost ? 'ssl' : 'normal') . '_vhost_' . $domain['domain'] . '.conf';
|
||||
if ($filename_only) {
|
||||
return $filename;
|
||||
}
|
||||
return FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $filename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,6 +556,10 @@ EOC;
|
||||
Settings::Set('system.le_froxlor_enabled', 0);
|
||||
}
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "Let's Encrypt deactivated for domain " . $domain);
|
||||
if (!defined('CRON_IS_FORCED') && !defined('CRON_DEBUG_FLAG')) {
|
||||
// email info to admin that lets encrypt has been disabled for this domain
|
||||
Cronjob::notifyMailToAdmin("Let's Encrypt has been deactivated for domain '" . $domain . "' due to failed dns validation (wrong or no IP address)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -586,11 +590,20 @@ EOC;
|
||||
$acmesh_cmd .= " --debug";
|
||||
}
|
||||
|
||||
$acme_result = FileDir::safe_exec($acmesh_cmd);
|
||||
$exit_code = null;
|
||||
$acme_result = FileDir::safe_exec($acmesh_cmd, $exit_code);
|
||||
// debug output of acme.sh run
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, implode("\n", $acme_result));
|
||||
|
||||
self::certToDb($certrow, $cronlog, $acme_result);
|
||||
if ($exit_code != 0) {
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Non-successful exit-code returned :(");
|
||||
if (!defined('CRON_IS_FORCED') && !defined('CRON_DEBUG_FLAG')) {
|
||||
Cronjob::notifyMailToAdmin("Let's Encrypt certificate could not be obtained for: " . implode(", ", $domains) . "\n\n" . implode("\n", $acme_result));
|
||||
}
|
||||
} else {
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Successful exit-code returned - storing certificate");
|
||||
self::certToDb($certrow, $cronlog, $acme_result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -336,24 +336,9 @@ class Lighttpd extends HttpConfigBase
|
||||
$_pos = strrpos($_tmp_path, '/');
|
||||
$_inc_path = substr($_tmp_path, $_pos + 1);
|
||||
|
||||
// maindomain
|
||||
if ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && ((int)$domain['ismainbutsubto'] == 0 || Domain::domainMainToSubExists($domain['ismainbutsubto']) == false)) {
|
||||
$vhost_no = '50';
|
||||
} elseif ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && (int)$domain['ismainbutsubto'] > 0) {
|
||||
// sub-but-main-domain
|
||||
$vhost_no = '51';
|
||||
} else {
|
||||
// subdomains
|
||||
// number of dots in a domain specifies it's position (and depth of subdomain) starting at 89 going downwards on higher depth
|
||||
$vhost_no = (string)(90 - substr_count($domain['domain'], ".") + 1);
|
||||
}
|
||||
|
||||
if ($ssl == '1') {
|
||||
$vhost_no = (int)$vhost_no += 10;
|
||||
}
|
||||
|
||||
$vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/vhosts/' . $vhost_no . '_' . $domain['domain'] . '.conf');
|
||||
$included_vhosts[] = $_inc_path . '/vhosts/' . $vhost_no . '_' . $domain['domain'] . '.conf';
|
||||
$filename = self::getVhostFilename($domain, ($ssl == '1'), true);
|
||||
$vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/vhosts/' . $filename);
|
||||
$included_vhosts[] = $_inc_path . '/vhosts/' . $filename;
|
||||
}
|
||||
|
||||
if (!isset($this->lighttpd_data[$vhost_filename])) {
|
||||
|
||||
@@ -467,26 +467,6 @@ class Nginx extends HttpConfigBase
|
||||
}
|
||||
}
|
||||
|
||||
protected function getVhostFilename($domain, $ssl_vhost = false)
|
||||
{
|
||||
if ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && ((int)$domain['ismainbutsubto'] == 0 || Domain::domainMainToSubExists($domain['ismainbutsubto']) == false)) {
|
||||
$vhost_no = '35';
|
||||
} elseif ((int)$domain['parentdomainid'] == 0 && Domain::isCustomerStdSubdomain((int)$domain['id']) == false && (int)$domain['ismainbutsubto'] > 0) {
|
||||
$vhost_no = '30';
|
||||
} else {
|
||||
// number of dots in a domain specifies it's position (and depth of subdomain) starting at 29 going downwards on higher depth
|
||||
$vhost_no = (string)(30 - substr_count($domain['domain'], ".") + 1);
|
||||
}
|
||||
|
||||
if ($ssl_vhost === true) {
|
||||
$vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $vhost_no . '_froxlor_ssl_vhost_' . $domain['domain'] . '.conf');
|
||||
} else {
|
||||
$vhost_filename = FileDir::makeCorrectFile(Settings::Get('system.apacheconf_vhost') . '/' . $vhost_no . '_froxlor_normal_vhost_' . $domain['domain'] . '.conf');
|
||||
}
|
||||
|
||||
return $vhost_filename;
|
||||
}
|
||||
|
||||
protected function getVhostContent($domain, $ssl_vhost = false)
|
||||
{
|
||||
if ($ssl_vhost === true && $domain['ssl'] != '1' && $domain['ssl_redirect'] != '1') {
|
||||
|
||||
@@ -289,7 +289,7 @@ pm.max_children = 1
|
||||
}
|
||||
}
|
||||
|
||||
// now check if 'sendmail_path' has not beed set in the custom-php.ini
|
||||
// now check if 'sendmail_path' has not been set in the custom-php.ini
|
||||
// if not we use our fallback-default as usual
|
||||
if (strpos($fpm_config, 'php_admin_value[sendmail_path]') === false) {
|
||||
$fpm_config .= 'php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f ' . $this->domain['email'] . "\n";
|
||||
|
||||
@@ -25,122 +25,84 @@
|
||||
|
||||
namespace Froxlor\Cron\System;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Cron\Forkable;
|
||||
use Froxlor\Cron\FroxlorCron;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\FileDir;
|
||||
use Froxlor\FroxlorLogger;
|
||||
use Froxlor\Settings;
|
||||
|
||||
class BackupCron extends FroxlorCron
|
||||
class ExportCron extends FroxlorCron
|
||||
{
|
||||
use Forkable;
|
||||
|
||||
public static function run()
|
||||
{
|
||||
// Check Traffic-Lock
|
||||
if (function_exists('pcntl_fork')) {
|
||||
$BackupLock = FileDir::makeCorrectFile(dirname(self::getLockfile()) . "/froxlor_cron_backup.lock");
|
||||
if (file_exists($BackupLock) && is_numeric($BackupPid = file_get_contents($BackupLock))) {
|
||||
if (function_exists('posix_kill')) {
|
||||
$BackupPidStatus = @posix_kill($BackupPid, 0);
|
||||
} else {
|
||||
system("kill -CHLD " . $BackupPid . " 1> /dev/null 2> /dev/null", $BackupPidStatus);
|
||||
$BackupPidStatus = !$BackupPidStatus;
|
||||
}
|
||||
if ($BackupPidStatus) {
|
||||
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Backup run already in progress');
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// Create Backup Log and Fork
|
||||
// We close the database - connection before we fork, so we don't share resources with the child
|
||||
Database::needRoot(false); // this forces the connection to be set to null
|
||||
$BackupPid = pcntl_fork();
|
||||
// Parent
|
||||
if ($BackupPid) {
|
||||
file_put_contents($BackupLock, $BackupPid);
|
||||
// unnecessary to recreate database connection here
|
||||
return 0;
|
||||
} elseif ($BackupPid == 0) {
|
||||
// Child
|
||||
posix_setsid();
|
||||
// re-create db
|
||||
Database::needRoot(false);
|
||||
} else {
|
||||
// Fork failed
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
if (extension_loaded('pcntl')) {
|
||||
$msg = "PHP compiled with pcntl but pcntl_fork function is not available.";
|
||||
} else {
|
||||
$msg = "PHP compiled without pcntl.";
|
||||
}
|
||||
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, $msg . " Not forking backup-cron, this may take a long time!");
|
||||
}
|
||||
|
||||
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'BackupCron: started - creating customer backup');
|
||||
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'ExportCron: started - creating customer data export');
|
||||
|
||||
$result_tasks_stmt = Database::query("
|
||||
SELECT * FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` = '20' ORDER BY `id` ASC
|
||||
");
|
||||
|
||||
$del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `id` = :id");
|
||||
|
||||
$cronlog = FroxlorLogger::getInstanceOf();
|
||||
$all_jobs = $result_tasks_stmt->fetchAll();
|
||||
foreach ($all_jobs as $row) {
|
||||
if ($row['data'] != '') {
|
||||
$row['data'] = json_decode($row['data'], true);
|
||||
}
|
||||
|
||||
if (is_array($row['data'])) {
|
||||
if (isset($row['data']['customerid']) && isset($row['data']['loginname']) && isset($row['data']['destdir'])) {
|
||||
$row['data']['destdir'] = FileDir::makeCorrectDir($row['data']['destdir']);
|
||||
$customerdocroot = FileDir::makeCorrectDir(Settings::Get('system.documentroot_prefix') . '/' . $row['data']['loginname'] . '/');
|
||||
|
||||
// create folder if not exists
|
||||
if (!file_exists($row['data']['destdir']) && $row['data']['destdir'] != '/' && $row['data']['destdir'] != Settings::Get('system.documentroot_prefix') && $row['data']['destdir'] != $customerdocroot) {
|
||||
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'Creating backup-destination path for customer: ' . escapeshellarg($row['data']['destdir']));
|
||||
FileDir::safe_exec('mkdir -p ' . escapeshellarg($row['data']['destdir']));
|
||||
}
|
||||
|
||||
self::createCustomerBackup($row['data'], $customerdocroot, $cronlog);
|
||||
}
|
||||
}
|
||||
|
||||
// remove entry
|
||||
Database::pexecute($del_stmt, [
|
||||
'id' => $row['id']
|
||||
]);
|
||||
}
|
||||
|
||||
if (function_exists('pcntl_fork')) {
|
||||
@unlink($BackupLock);
|
||||
die();
|
||||
if (!empty($all_jobs)) {
|
||||
self::runFork([self::class, 'handle'], $all_jobs);
|
||||
}
|
||||
}
|
||||
|
||||
public static function handle(array $row)
|
||||
{
|
||||
$del_stmt = Database::prepare("DELETE FROM `" . TABLE_PANEL_TASKS . "` WHERE `id` = :id");
|
||||
$cronlog = FroxlorLogger::getInstanceOf();
|
||||
|
||||
if ($row['data'] != '') {
|
||||
$row['data'] = json_decode($row['data'], true);
|
||||
}
|
||||
|
||||
if (is_array($row['data'])) {
|
||||
if (isset($row['data']['customerid']) && isset($row['data']['loginname']) && isset($row['data']['destdir'])) {
|
||||
$row['data']['destdir'] = FileDir::makeCorrectDir($row['data']['destdir']);
|
||||
$customerdocroot = FileDir::makeCorrectDir(Settings::Get('system.documentroot_prefix') . '/' . $row['data']['loginname'] . '/');
|
||||
|
||||
// create folder if not exists
|
||||
if (!file_exists($row['data']['destdir']) && $row['data']['destdir'] != '/' && $row['data']['destdir'] != Settings::Get('system.documentroot_prefix') && $row['data']['destdir'] != $customerdocroot) {
|
||||
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating data export destination path for customer: ' . escapeshellarg($row['data']['destdir']));
|
||||
FileDir::safe_exec('mkdir -p ' . escapeshellarg($row['data']['destdir']));
|
||||
}
|
||||
|
||||
self::createCustomerExport($row['data'], $customerdocroot, $cronlog);
|
||||
}
|
||||
}
|
||||
|
||||
// remove entry
|
||||
Database::pexecute($del_stmt, [
|
||||
'id' => $row['id']
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* depending on the give choice, the customers web-data, email-data and databases are being backup'ed
|
||||
* depending on the give choice, the customers web-data, email-data and databases are being exported
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function createCustomerBackup($data = null, $customerdocroot = null, &$cronlog = null)
|
||||
private static function createCustomerExport($data = null, $customerdocroot = null, &$cronlog = null)
|
||||
{
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating Backup for user "' . $data['loginname'] . '"');
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, 'Creating data export for user "' . $data['loginname'] . '"');
|
||||
|
||||
// create tmp folder
|
||||
$tmpdir = FileDir::makeCorrectDir($data['destdir'] . '/.tmp/');
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating tmp-folder "' . $tmpdir . '"');
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mkdir -p ' . escapeshellarg($tmpdir));
|
||||
FileDir::safe_exec('mkdir -p ' . escapeshellarg($tmpdir));
|
||||
$create_backup_tar_data = "";
|
||||
$create_export_tar_data = "";
|
||||
|
||||
// MySQL databases
|
||||
if ($data['backup_dbs'] == 1) {
|
||||
if ($data['dump_dbs'] == 1) {
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating mysql-folder "' . FileDir::makeCorrectDir($tmpdir . '/mysql') . '"');
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/mysql')));
|
||||
FileDir::safe_exec('mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/mysql')));
|
||||
@@ -152,7 +114,7 @@ class BackupCron extends FroxlorCron
|
||||
]);
|
||||
|
||||
$has_dbs = false;
|
||||
$current_dbserver = null;
|
||||
$current_dbserver = -1;
|
||||
while ($row = $sel_stmt->fetch()) {
|
||||
// Get sql_root data for the specific database-server the database resides on
|
||||
if ($current_dbserver != $row['dbserver']) {
|
||||
@@ -180,16 +142,18 @@ class BackupCron extends FroxlorCron
|
||||
}
|
||||
|
||||
if ($has_dbs) {
|
||||
$create_backup_tar_data .= './mysql ';
|
||||
$create_export_tar_data .= './mysql ';
|
||||
}
|
||||
|
||||
unlink($mysqlcnf_file);
|
||||
if (file_exists($mysqlcnf_file)) {
|
||||
unlink($mysqlcnf_file);
|
||||
}
|
||||
|
||||
unset($sql_root);
|
||||
}
|
||||
|
||||
// E-mail data
|
||||
if ($data['backup_mail'] == 1) {
|
||||
if ($data['dump_mail'] == 1) {
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating mail-folder "' . FileDir::makeCorrectDir($tmpdir . '/mail') . '"');
|
||||
FileDir::safe_exec('mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/mail')));
|
||||
|
||||
@@ -209,28 +173,41 @@ class BackupCron extends FroxlorCron
|
||||
if (!empty($tar_file_list)) {
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfvz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/mail/' . $data['loginname'] . '-mail.tar.gz')) . ' -C ' . escapeshellarg($mail_homedir) . ' ' . trim($tar_file_list));
|
||||
FileDir::safe_exec('tar cfz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/mail/' . $data['loginname'] . '-mail.tar.gz')) . ' -C ' . escapeshellarg($mail_homedir) . ' ' . trim($tar_file_list));
|
||||
$create_backup_tar_data .= './mail ';
|
||||
$create_export_tar_data .= './mail ';
|
||||
}
|
||||
}
|
||||
|
||||
// Web data
|
||||
if ($data['backup_web'] == 1) {
|
||||
if ($data['dump_web'] == 1) {
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Creating web-folder "' . FileDir::makeCorrectDir($tmpdir . '/web') . '"');
|
||||
FileDir::safe_exec('mkdir -p ' . escapeshellarg(FileDir::makeCorrectDir($tmpdir . '/web')));
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/web/' . $data['loginname'] . '-web.tar.gz')) . ' --exclude=' . escapeshellarg(str_replace($customerdocroot, "./", FileDir::makeCorrectFile($tmpdir . '/*'))) . ' --exclude=' . escapeshellarg(str_replace($customerdocroot, "./", substr(FileDir::makeCorrectDir($tmpdir), 0, -1))) . ' -C ' . escapeshellarg($customerdocroot) . ' .');
|
||||
FileDir::safe_exec('tar cfz ' . escapeshellarg(FileDir::makeCorrectFile($tmpdir . '/web/' . $data['loginname'] . '-web.tar.gz')) . ' --exclude=' . escapeshellarg(str_replace($customerdocroot, "./", FileDir::makeCorrectFile($tmpdir . '/*'))) . ' --exclude=' . escapeshellarg(str_replace($customerdocroot, "./", substr(FileDir::makeCorrectFile($tmpdir), 0, -1))) . ' -C ' . escapeshellarg($customerdocroot) . ' .');
|
||||
$create_backup_tar_data .= './web ';
|
||||
$create_export_tar_data .= './web ';
|
||||
}
|
||||
|
||||
if (!empty($create_backup_tar_data)) {
|
||||
$backup_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-backup_' . date('YmdHi', time()) . '.tar.gz');
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating backup-file "' . $backup_file . '"');
|
||||
// pack all archives in tmp-dir to one
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz ' . escapeshellarg($backup_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data));
|
||||
FileDir::safe_exec('tar cfz ' . escapeshellarg($backup_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_backup_tar_data));
|
||||
if (!empty($create_export_tar_data)) {
|
||||
// set owner to customer
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> chown -R ' . (int)$data['uid'] . ':' . (int)$data['gid'] . ' ' . escapeshellarg($tmpdir));
|
||||
FileDir::safe_exec('chown -R ' . (int)$data['uid'] . ':' . (int)$data['gid'] . ' ' . escapeshellarg($tmpdir));
|
||||
// create tar-file
|
||||
$export_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-export_' . date('YmdHi', time()) . '.tar.gz' . (!empty($data['pgp_public_key']) ? '.gpg' : ''));
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating export-file "' . $export_file . '"');
|
||||
if (!empty($data['pgp_public_key'])) {
|
||||
// pack all archives in tmp-dir to one archive and encrypt it with gpg
|
||||
$recipient_file = FileDir::makeCorrectFile($tmpdir . '/' . $data['loginname'] . '-recipients.gpg');
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Creating recipient-file "' . $recipient_file . '"');
|
||||
file_put_contents($recipient_file, $data['pgp_public_key']);
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data) . ' | gpg --encrypt --recipient-file ' . escapeshellarg($recipient_file) . ' --output ' . escapeshellarg($export_file) . ' --trust-model always --batch --yes');
|
||||
FileDir::safe_exec('tar cfz - -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data) . ' | gpg --encrypt --recipient-file ' . escapeshellarg($recipient_file) . ' --output ' . escapeshellarg($export_file) . ' --trust-model always --batch --yes', $return_value, ['|']);
|
||||
} else {
|
||||
// pack all archives in tmp-dir to one archive
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> tar cfz ' . escapeshellarg($export_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data));
|
||||
FileDir::safe_exec('tar cfz ' . escapeshellarg($export_file) . ' -C ' . escapeshellarg($tmpdir) . ' ' . trim($create_export_tar_data));
|
||||
}
|
||||
// move to destination directory
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mv ' . escapeshellarg($backup_file) . ' ' . escapeshellarg($data['destdir']));
|
||||
FileDir::safe_exec('mv ' . escapeshellarg($backup_file) . ' ' . escapeshellarg($data['destdir']));
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mv ' . escapeshellarg($export_file) . ' ' . escapeshellarg($data['destdir']));
|
||||
FileDir::safe_exec('mv ' . escapeshellarg($export_file) . ' ' . escapeshellarg($data['destdir']));
|
||||
// remove tmp-files
|
||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> rm -rf ' . escapeshellarg($tmpdir));
|
||||
FileDir::safe_exec('rm -rf ' . escapeshellarg($tmpdir));
|
||||
@@ -46,7 +46,7 @@ class TasksCron extends FroxlorCron
|
||||
* LOOK INTO TASKS TABLE TO SEE IF THERE ARE ANY UNDONE JOBS
|
||||
*/
|
||||
self::$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "TasksCron: Searching for tasks to do");
|
||||
// no type 99 (regenerate cron.d-file) and no type 20 (customer backup)
|
||||
// no type 99 (regenerate cron.d-file) and no type 20 (customer data export)
|
||||
// order by type descending to re-create bind and then webserver at the end
|
||||
$result_tasks_stmt = Database::query("
|
||||
SELECT `id`, `type`, `data` FROM `" . TABLE_PANEL_TASKS . "` WHERE `type` <> '99' AND `type` <> '20' ORDER BY `type` DESC, `id` ASC
|
||||
|
||||
@@ -82,9 +82,9 @@ final class TaskId
|
||||
const DELETE_DOMAIN_SSL = 12;
|
||||
|
||||
/**
|
||||
* TYPE=20 COSTUMERBACKUP
|
||||
* TYPE=20 CUSTUMER DATA DUMP
|
||||
*/
|
||||
const CREATE_CUSTOMER_BACKUP = 20;
|
||||
const CREATE_CUSTOMER_DATADUMP = 20;
|
||||
|
||||
/**
|
||||
* TYPE=99 REGENERATE CRON
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace Froxlor\Cron\Traffic;
|
||||
* @author Froxlor team <team@froxlor.org> (2010-)
|
||||
*/
|
||||
|
||||
use Froxlor\Cron\Forkable;
|
||||
use Froxlor\Cron\FroxlorCron;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\FileDir;
|
||||
@@ -42,51 +43,15 @@ use PDO;
|
||||
|
||||
class TrafficCron extends FroxlorCron
|
||||
{
|
||||
use Forkable;
|
||||
|
||||
public static function run()
|
||||
{
|
||||
// Check Traffic-Lock
|
||||
if (function_exists('pcntl_fork') && !defined('CRON_NOFORK_FLAG')) {
|
||||
$TrafficLock = FileDir::makeCorrectFile("/var/run/froxlor_cron_traffic.lock");
|
||||
if (file_exists($TrafficLock) && is_numeric($TrafficPid = file_get_contents($TrafficLock))) {
|
||||
if (function_exists('posix_kill')) {
|
||||
$TrafficPidStatus = @posix_kill($TrafficPid, 0);
|
||||
} else {
|
||||
system("kill -CHLD " . $TrafficPid . " 1> /dev/null 2> /dev/null", $TrafficPidStatus);
|
||||
$TrafficPidStatus = !$TrafficPidStatus;
|
||||
}
|
||||
if ($TrafficPidStatus) {
|
||||
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, 'Traffic Run already in progress');
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// Create Traffic Log and Fork
|
||||
// We close the database - connection before we fork, so we don't share resources with the child
|
||||
Database::needRoot(false); // this forces the connection to be set to null
|
||||
$TrafficPid = pcntl_fork();
|
||||
// Parent
|
||||
if ($TrafficPid) {
|
||||
file_put_contents($TrafficLock, $TrafficPid);
|
||||
// unnecessary to recreate database connection here
|
||||
return 0;
|
||||
} elseif ($TrafficPid == 0) {
|
||||
// Child
|
||||
posix_setsid();
|
||||
// re-create db
|
||||
Database::needRoot(false);
|
||||
} else {
|
||||
// Fork failed
|
||||
return 1;
|
||||
}
|
||||
} elseif (!defined('CRON_NOFORK_FLAG')) {
|
||||
if (extension_loaded('pcntl')) {
|
||||
$msg = "PHP compiled with pcntl but pcntl_fork function is not available.";
|
||||
} else {
|
||||
$msg = "PHP compiled without pcntl.";
|
||||
}
|
||||
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, $msg . " Not forking traffic-cron, this may take a long time!");
|
||||
}
|
||||
self::runFork([self::class, 'handle']);
|
||||
}
|
||||
|
||||
public static function handle()
|
||||
{
|
||||
/**
|
||||
* TRAFFIC AND DISKUSAGE MEASURE
|
||||
*/
|
||||
@@ -611,11 +576,6 @@ class TrafficCron extends FroxlorCron
|
||||
}
|
||||
|
||||
Database::query("UPDATE `" . TABLE_PANEL_SETTINGS . "` SET `value` = UNIX_TIMESTAMP() WHERE `settinggroup` = 'system' AND `varname` = 'last_traffic_run'");
|
||||
|
||||
if (function_exists('pcntl_fork') && !defined('CRON_NOFORK_FLAG')) {
|
||||
@unlink($TrafficLock);
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,10 +25,13 @@
|
||||
|
||||
namespace Froxlor;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Api\Commands\Customers;
|
||||
use Froxlor\Api\Commands\SubDomains;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\UI\Collection;
|
||||
use Froxlor\UI\Response;
|
||||
use RobThree\Auth\TwoFactorAuthException;
|
||||
|
||||
/**
|
||||
* Class to manage the current user / session
|
||||
@@ -144,7 +147,7 @@ class CurrentUser
|
||||
$result_stmt = Database::prepare("
|
||||
SELECT COUNT(`id`) as emaildomains
|
||||
FROM `" . TABLE_PANEL_DOMAINS . "`
|
||||
WHERE `customerid`= :cid AND `isemaildomain` = '1'
|
||||
WHERE `customerid`= :cid AND `isemaildomain` = '1' AND `deactivated` = '0'
|
||||
");
|
||||
$result = Database::pexecute_first($result_stmt, [
|
||||
"cid" => $_SESSION['userinfo']['customerid']
|
||||
@@ -154,16 +157,84 @@ class CurrentUser
|
||||
if (Settings::IsInList('panel.customer_hide_options', 'domains')) {
|
||||
$addition = false;
|
||||
} else {
|
||||
$parentDomainCollection = (new Collection(SubDomains::class, $_SESSION['userinfo'],
|
||||
['sql_search' => ['d.parentdomainid' => 0]]));
|
||||
$parentDomainCollection = (new Collection(
|
||||
SubDomains::class,
|
||||
$_SESSION['userinfo'],
|
||||
['sql_search' => [
|
||||
'd.parentdomainid' => 0,
|
||||
'd.deactivated' => 0,
|
||||
'd.id' => ['op' => '<>', 'value' => $_SESSION['userinfo']['standardsubdomain']]
|
||||
]
|
||||
]
|
||||
));
|
||||
$addition = $parentDomainCollection->count() != 0;
|
||||
}
|
||||
} elseif ($resource == 'domains') {
|
||||
$customerCollection = (new Collection(Customers::class, $_SESSION['userinfo']));
|
||||
$addition = $customerCollection != 0;
|
||||
$addition = $customerCollection->count() != 0;
|
||||
}
|
||||
|
||||
return ($_SESSION['userinfo'][$resource . '_used'] < $_SESSION['userinfo'][$resource] || $_SESSION['userinfo'][$resource] == '-1') && $addition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws TwoFactorAuthException
|
||||
*/
|
||||
public static function sendOtpEmail()
|
||||
{
|
||||
global $mail;
|
||||
|
||||
if (self::getField('type_2fa') == 1) {
|
||||
// generate code
|
||||
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
|
||||
$code = $tfa->getCode($tfa->createSecret());
|
||||
// set code for user
|
||||
$table = TABLE_PANEL_CUSTOMERS;
|
||||
$uid = 'customerid';
|
||||
if (self::isAdmin()) {
|
||||
$table = TABLE_PANEL_ADMINS;
|
||||
$uid = 'adminid';
|
||||
}
|
||||
$stmt = Database::prepare("UPDATE $table SET `data_2fa` = :d2fa WHERE `$uid` = :uid");
|
||||
Database::pexecute($stmt, [
|
||||
"d2fa" => $code,
|
||||
"uid" => self::getField($uid)
|
||||
]);
|
||||
// build up & send email
|
||||
$_mailerror = false;
|
||||
$mailerr_msg = "";
|
||||
$replace_arr = [
|
||||
'CODE' => $code
|
||||
];
|
||||
$mail_body = html_entity_decode(PhpHelper::replaceVariables(lng('mails.2fa.mailbody'), $replace_arr));
|
||||
|
||||
try {
|
||||
$mail->Subject = lng('mails.2fa.subject');
|
||||
$mail->AltBody = $mail_body;
|
||||
$mail->MsgHTML(str_replace("\n", "<br />", $mail_body));
|
||||
$mail->AddAddress(self::getField('email'), User::getCorrectUserSalutation(self::getData()));
|
||||
$mail->Send();
|
||||
} catch (\PHPMailer\PHPMailer\Exception $e) {
|
||||
$mailerr_msg = $e->errorMessage();
|
||||
$_mailerror = true;
|
||||
} catch (Exception $e) {
|
||||
$mailerr_msg = $e->getMessage();
|
||||
$_mailerror = true;
|
||||
}
|
||||
|
||||
if ($_mailerror) {
|
||||
$rstlog = FroxlorLogger::getInstanceOf([
|
||||
'loginname' => '2fa code-sending'
|
||||
]);
|
||||
$rstlog->logAction(FroxlorLogger::ADM_ACTION, LOG_ERR, "Error sending mail: " . $mailerr_msg);
|
||||
Response::redirectTo('index.php', [
|
||||
'showmessage' => '4',
|
||||
'customermail' => self::getField('email')
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
|
||||
$mail->ClearAddresses();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,51 +235,30 @@ class Domain
|
||||
}
|
||||
|
||||
/**
|
||||
* check whether a domain has subdomains added as full-domains
|
||||
* #329
|
||||
* get ids of domains that are main domains but a subdomain of another main domain (for DNS)
|
||||
*
|
||||
* @param int $id domain-id
|
||||
* @param int $id main-domain to check
|
||||
*
|
||||
* @return bool
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function domainHasMainSubDomains(int $id): bool
|
||||
public static function getMainSubdomainIds(int $id): array
|
||||
{
|
||||
$result_stmt = Database::prepare("
|
||||
SELECT COUNT(`id`) as `mainsubs` FROM `" . TABLE_PANEL_DOMAINS . "`
|
||||
WHERE `ismainbutsubto` = :id");
|
||||
$result = Database::pexecute_first($result_stmt, [
|
||||
'id' => $id
|
||||
]);
|
||||
|
||||
if ($result && isset($result['mainsubs'])) {
|
||||
return $result['mainsubs'] > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* check whether a subof-domain exists
|
||||
* #329
|
||||
*
|
||||
* @param int $id subof-domain-id
|
||||
*
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function domainMainToSubExists(int $id): bool
|
||||
{
|
||||
$result_stmt = Database::prepare("
|
||||
SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `id` = :id");
|
||||
SELECT id
|
||||
FROM `" . TABLE_PANEL_DOMAINS . "`
|
||||
WHERE
|
||||
isbinddomain = 1 AND
|
||||
domain LIKE CONCAT('%.', ( SELECT d.domain FROM `" . TABLE_PANEL_DOMAINS . "` AS d WHERE d.id = :id ))
|
||||
");
|
||||
Database::pexecute($result_stmt, [
|
||||
'id' => $id
|
||||
]);
|
||||
$result = $result_stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($result && isset($result['id'])) {
|
||||
return $result['id'] > 0;
|
||||
$result = [];
|
||||
while ($entry = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$result = $entry['id'];
|
||||
}
|
||||
return false;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -219,7 +219,7 @@ class FileDir
|
||||
}
|
||||
|
||||
// execute the command and return output
|
||||
$return = '';
|
||||
$return = [];
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
if ($return_value == false) {
|
||||
|
||||
@@ -31,10 +31,10 @@ final class Froxlor
|
||||
{
|
||||
|
||||
// Main version variable
|
||||
const VERSION = '2.0.24';
|
||||
const VERSION = '2.1.0-dev1';
|
||||
|
||||
// Database version (YYYYMMDDC where C is a daily counter)
|
||||
const DBVERSION = '202304260';
|
||||
const DBVERSION = '202305240';
|
||||
|
||||
// Distribution branding-tag (used for Debian etc.)
|
||||
const BRANDING = '';
|
||||
|
||||
@@ -43,7 +43,7 @@ class Install
|
||||
public $formfield;
|
||||
public string $requiredVersion = '7.4.0';
|
||||
public array $requiredExtensions = ['session', 'ctype', 'xml', 'filter', 'posix', 'mbstring', 'curl', 'gmp', 'json', 'gd'];
|
||||
public array $suggestedExtensions = ['bcmath', 'zip'];
|
||||
public array $suggestedExtensions = ['bcmath', 'zip', 'gnupg'];
|
||||
public array $suggestions = [];
|
||||
public array $criticals = [];
|
||||
public array $loadedExtensions;
|
||||
|
||||
@@ -421,6 +421,7 @@ class Core
|
||||
|
||||
$this->updateSetting($upd_stmt, $this->validatedData['activate_newsfeed'], 'admin', 'show_news_feed');
|
||||
$this->updateSetting($upd_stmt, dirname(__FILE__, 5), 'system', 'letsencryptchallengepath');
|
||||
$this->updateSetting($upd_stmt, dirname(__FILE__, 5) . '/templates/misc/deactivated/', 'system', 'deactivateddocroot');
|
||||
|
||||
// insert the lastcronrun to be the installation date
|
||||
$this->updateSetting($upd_stmt, time(), 'system', 'lastcronrun');
|
||||
|
||||
@@ -129,7 +129,8 @@ class Settings
|
||||
{
|
||||
// set defaults
|
||||
self::$conf = [
|
||||
'enable_webupdate' => false
|
||||
'enable_webupdate' => false,
|
||||
'disable_otp_security_check' => false,
|
||||
];
|
||||
|
||||
$configfile = Froxlor::getInstallDir() . '/lib/config.inc.php';
|
||||
|
||||
@@ -211,10 +211,10 @@ class Cronjob
|
||||
'type' => TaskId::DELETE_DOMAIN_SSL,
|
||||
'data' => $data
|
||||
]);
|
||||
} elseif ($type == TaskId::CREATE_CUSTOMER_BACKUP && isset($params[0]) && is_array($params[0])) {
|
||||
} elseif ($type == TaskId::CREATE_CUSTOMER_DATADUMP && isset($params[0]) && is_array($params[0])) {
|
||||
$data = json_encode($params[0]);
|
||||
Database::pexecute($ins_stmt, [
|
||||
'type' => TaskId::CREATE_CUSTOMER_BACKUP,
|
||||
'type' => TaskId::CREATE_CUSTOMER_DATADUMP,
|
||||
'data' => $data
|
||||
]);
|
||||
}
|
||||
@@ -310,42 +310,37 @@ class Cronjob
|
||||
}
|
||||
|
||||
/**
|
||||
* Cronjob function to end a cronjob in a critical condition
|
||||
* but not without sending a notification mail to the admin
|
||||
* Send notification to system admin via email
|
||||
*
|
||||
* @param string $message
|
||||
* @param string $subject
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function dieWithMail(string $message, string $subject = "[froxlor] Cronjob error")
|
||||
public static function notifyMailToAdmin(string $message, string $subject = "[froxlor] Important notice")
|
||||
{
|
||||
if (Settings::Get('system.send_cron_errors') == '1') {
|
||||
$_mail = new Mailer(true);
|
||||
$_mailerror = false;
|
||||
$mailerr_msg = "";
|
||||
try {
|
||||
$_mail->Subject = $subject;
|
||||
$_mail->AltBody = $message;
|
||||
$_mail->MsgHTML(nl2br($message));
|
||||
$_mail->AddAddress(Settings::Get('panel.adminmail'), Settings::Get('panel.adminmail_defname'));
|
||||
$_mail->Send();
|
||||
} catch (\PHPMailer\PHPMailer\Exception $e) {
|
||||
$mailerr_msg = $e->errorMessage();
|
||||
$_mailerror = true;
|
||||
} catch (Exception $e) {
|
||||
$mailerr_msg = $e->getMessage();
|
||||
$_mailerror = true;
|
||||
}
|
||||
|
||||
$_mail->ClearAddresses();
|
||||
|
||||
if ($_mailerror) {
|
||||
echo 'Error sending mail: ' . $mailerr_msg . "\n";
|
||||
}
|
||||
$mail = new Mailer(true);
|
||||
$mailerror = false;
|
||||
$mailerr_msg = "";
|
||||
try {
|
||||
$mail->Subject = $subject;
|
||||
$mail->AltBody = $message;
|
||||
$mail->MsgHTML(nl2br($message));
|
||||
$mail->AddAddress(Settings::Get('panel.adminmail'), Settings::Get('panel.adminmail_defname'));
|
||||
$mail->Send();
|
||||
} catch (\PHPMailer\PHPMailer\Exception $e) {
|
||||
$mailerr_msg = $e->errorMessage();
|
||||
$mailerror = true;
|
||||
} catch (Exception $e) {
|
||||
$mailerr_msg = $e->getMessage();
|
||||
$mailerror = true;
|
||||
}
|
||||
|
||||
die($message);
|
||||
$mail->ClearAddresses();
|
||||
|
||||
if ($mailerror) {
|
||||
echo 'Error sending mail: ' . $mailerr_msg . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
58
lib/Froxlor/System/Markdown.php
Normal file
58
lib/Froxlor/System/Markdown.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Froxlor project.
|
||||
* Copyright (c) 2010 the Froxlor Team (see authors).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, you can also view it online at
|
||||
* https://files.froxlor.org/misc/COPYING.txt
|
||||
*
|
||||
* @copyright the authors
|
||||
* @author Froxlor team <team@froxlor.org>
|
||||
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
|
||||
*/
|
||||
|
||||
namespace Froxlor\System;
|
||||
|
||||
use League\CommonMark\Exception\CommonMarkException;
|
||||
use League\CommonMark\GithubFlavoredMarkdownConverter;
|
||||
|
||||
class Markdown
|
||||
{
|
||||
|
||||
private static $converter = null;
|
||||
|
||||
public static function converter(): ?GithubFlavoredMarkdownConverter
|
||||
{
|
||||
if (is_null(self::$converter)) {
|
||||
self::$converter = new GithubFlavoredMarkdownConverter([
|
||||
'html_input' => 'strip',
|
||||
'allow_unsafe_links' => false,
|
||||
]);
|
||||
}
|
||||
return self::$converter;
|
||||
}
|
||||
|
||||
public static function cleanCustomNotes(string $note = ""): string
|
||||
{
|
||||
if (!empty($note)) {
|
||||
try {
|
||||
$note = self::converter()->convert($note)->getContent();
|
||||
} catch (CommonMarkException $e) {
|
||||
$note = "";
|
||||
}
|
||||
}
|
||||
return $note;
|
||||
}
|
||||
}
|
||||
@@ -25,10 +25,10 @@
|
||||
|
||||
namespace Froxlor\Traffic;
|
||||
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\Api\Commands\Customers;
|
||||
use Froxlor\UI\Collection;
|
||||
use Froxlor\Api\Commands\Traffic as TrafficAPI;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\UI\Collection;
|
||||
|
||||
class Traffic
|
||||
{
|
||||
@@ -38,10 +38,10 @@ class Traffic
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getCustomerStats(array $userinfo, string $range = null): array
|
||||
public static function getCustomerStats(array $userinfo, string $range = null, bool $overview = false): array
|
||||
{
|
||||
$trafficCollectionObj = (new Collection(TrafficAPI::class, $userinfo,
|
||||
self::getParamsByRange($range, ['customer_traffic' => true,])));
|
||||
self::getParamsByRange($range, ['customer_traffic' => true])));
|
||||
if ($userinfo['adminsession'] == 1) {
|
||||
$trafficCollectionObj->has('customer', Customers::class, 'customerid', 'customerid');
|
||||
}
|
||||
@@ -53,27 +53,36 @@ class Traffic
|
||||
$months = [];
|
||||
$days = [];
|
||||
foreach ($trafficCollection['data']['list'] as $item) {
|
||||
$http = $item['http'];
|
||||
$ftp = ($item['ftp_up'] + $item['ftp_down']);
|
||||
$mail = $item['mail'];
|
||||
$total = $http + $ftp + $mail;
|
||||
|
||||
// per user total
|
||||
$users[$item['customerid']]['loginname'] = $item['customer']['loginname'];
|
||||
$users[$item['customerid']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
|
||||
$users[$item['customerid']]['http'] += $item['http'];
|
||||
$users[$item['customerid']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
|
||||
$users[$item['customerid']]['mail'] += $item['mail'];
|
||||
// per year
|
||||
$years[$item['year']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
|
||||
$years[$item['year']]['http'] += $item['http'];
|
||||
$years[$item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
|
||||
$years[$item['year']]['mail'] += $item['mail'];
|
||||
// per month
|
||||
$months[$item['month'] . '/' . $item['year']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
|
||||
$months[$item['month'] . '/' . $item['year']]['http'] += $item['http'];
|
||||
$months[$item['month'] . '/' . $item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
|
||||
$months[$item['month'] . '/' . $item['year']]['mail'] += $item['mail'];
|
||||
// per day
|
||||
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['total'] += ($item['http'] + $item['ftp_up'] + $item['ftp_down'] + $item['mail']);
|
||||
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['http'] += $item['http'];
|
||||
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['ftp'] += ($item['ftp_up'] + $item['ftp_down']);
|
||||
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['mail'] += $item['mail'];
|
||||
if ($userinfo['adminsession'] == 1) {
|
||||
$users[$item['customerid']]['loginname'] = $item['customer']['loginname'];
|
||||
}
|
||||
$users[$item['customerid']]['total'] += $total;
|
||||
$users[$item['customerid']]['http'] += $http;
|
||||
$users[$item['customerid']]['ftp'] += $ftp;
|
||||
$users[$item['customerid']]['mail'] += $mail;
|
||||
if (!$overview) {
|
||||
// per year
|
||||
$years[$item['year']]['total'] += $total;
|
||||
$years[$item['year']]['http'] += $http;
|
||||
$years[$item['year']]['ftp'] += $ftp;
|
||||
$years[$item['year']]['mail'] += $mail;
|
||||
// per month
|
||||
$months[$item['month'] . '/' . $item['year']]['total'] += $total;
|
||||
$months[$item['month'] . '/' . $item['year']]['http'] += $http;
|
||||
$months[$item['month'] . '/' . $item['year']]['ftp'] += $ftp;
|
||||
$months[$item['month'] . '/' . $item['year']]['mail'] += $mail;
|
||||
// per day
|
||||
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['total'] += $total;
|
||||
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['http'] += $http;
|
||||
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['ftp'] += $ftp;
|
||||
$days[$item['day'] . '.' . $item['month'] . '.' . $item['year']]['mail'] += $mail;
|
||||
}
|
||||
}
|
||||
|
||||
// calculate overview for given range from users
|
||||
@@ -85,10 +94,13 @@ class Traffic
|
||||
$metrics['mail'] += $user['mail'];
|
||||
}
|
||||
|
||||
// get all possible years for filter
|
||||
$sel_stmt = Database::prepare("SELECT DISTINCT year FROM `" . TABLE_PANEL_TRAFFIC . "` WHERE 1 ORDER BY `year` DESC");
|
||||
Database::pexecute($sel_stmt);
|
||||
$years_avail = $sel_stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$years_avail = [];
|
||||
if (!$overview) {
|
||||
// get all possible years for filter
|
||||
$sel_stmt = Database::prepare("SELECT DISTINCT year FROM `" . TABLE_PANEL_TRAFFIC . "` WHERE 1 ORDER BY `year` DESC");
|
||||
Database::pexecute($sel_stmt);
|
||||
$years_avail = $sel_stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
return [
|
||||
'metrics' => $metrics,
|
||||
|
||||
@@ -26,17 +26,19 @@
|
||||
namespace Froxlor\UI\Callbacks;
|
||||
|
||||
use Froxlor\Settings;
|
||||
use Froxlor\System\Markdown;
|
||||
|
||||
class Customer
|
||||
{
|
||||
public static function isLocked(array $attributes)
|
||||
public static function isLocked(array $attributes): bool
|
||||
{
|
||||
return $attributes['fields']['loginfail_count'] >= Settings::Get('login.maxloginattempts')
|
||||
&& $attributes['fields']['lastlogin_fail'] > (time() - Settings::Get('login.deactivatetime'));
|
||||
}
|
||||
|
||||
public static function hasNote(array $attributes)
|
||||
public static function hasNote(array $attributes): bool
|
||||
{
|
||||
return !empty($attributes['fields']['custom_notes']);
|
||||
$cleanNote = Markdown::cleanCustomNotes($attributes['fields']['custom_notes'] ?? "");
|
||||
return !empty($cleanNote);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
namespace Froxlor\UI\Callbacks;
|
||||
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\Domain\Domain as DDomain;
|
||||
use Froxlor\FileDir;
|
||||
use Froxlor\Settings;
|
||||
@@ -51,6 +52,9 @@ class Domain
|
||||
public static function domainTarget(array $attributes)
|
||||
{
|
||||
if (empty($attributes['fields']['aliasdomain'])) {
|
||||
if ($attributes['fields']['deactivated']) {
|
||||
return lng('admin.deactivated');
|
||||
}
|
||||
// path or redirect
|
||||
if (preg_match('/^https?\:\/\//', $attributes['fields']['documentroot'])) {
|
||||
return [
|
||||
@@ -76,11 +80,11 @@ class Domain
|
||||
{
|
||||
$result = '';
|
||||
if ($attributes['fields']['parentdomainid'] != 0) {
|
||||
$result = '<i class="fa-solid fa-turn-up me-2 fa-rotate-90 opacity-50"></i>';
|
||||
$result = '<i class="fa-solid fa-turn-up me-2 fa-rotate-90 opacity-50"></i>';
|
||||
}
|
||||
$result .= '<a href="http://' . $attributes['data'] . '" target="_blank">' . $attributes['data'] . '</a>';
|
||||
// check for statistics if parentdomainid==0 to show stats-link for customers
|
||||
if ((int)UI::getCurrentUser()['adminsession'] == 0 && $attributes['fields']['parentdomainid'] == 0) {
|
||||
if ((int)UI::getCurrentUser()['adminsession'] == 0 && $attributes['fields']['parentdomainid'] == 0 && $attributes['fields']['deactivated'] == 0) {
|
||||
$statsapp = Settings::Get('system.traffictool');
|
||||
$result .= ' <a href="http://' . $attributes['data'] . '/' . $statsapp . '" rel="external" target="_blank" title="' . lng('domains.statstics') . '"><i class="fa-solid fa-chart-line text-secondary"></i></a>';
|
||||
}
|
||||
@@ -95,12 +99,12 @@ class Domain
|
||||
|
||||
public static function canEdit(array $attributes): bool
|
||||
{
|
||||
return (bool)$attributes['fields']['caneditdomain'];
|
||||
return (bool)($attributes['fields']['caneditdomain'] && !$attributes['fields']['deactivated']);
|
||||
}
|
||||
|
||||
public static function canViewLogs(array $attributes): bool
|
||||
{
|
||||
if ((int)$attributes['fields']['email_only'] == 0) {
|
||||
if ((int)$attributes['fields']['email_only'] == 0 && !$attributes['fields']['deactivated']) {
|
||||
if ((int)UI::getCurrentUser()['adminsession'] == 0 && (bool)UI::getCurrentUser()['logviewenabled']) {
|
||||
return true;
|
||||
} elseif ((int)UI::getCurrentUser()['adminsession'] == 1) {
|
||||
@@ -129,7 +133,8 @@ class Domain
|
||||
&& UI::getCurrentUser()['dnsenabled'] == '1'
|
||||
&& $attributes['fields']['caneditdomain'] == '1'
|
||||
&& Settings::Get('system.bind_enable') == '1'
|
||||
&& Settings::Get('system.dnsenabled') == '1';
|
||||
&& Settings::Get('system.dnsenabled') == '1'
|
||||
&& !$attributes['fields']['deactivated'];
|
||||
}
|
||||
|
||||
public static function adminCanEditDNS(array $attributes): bool
|
||||
@@ -152,6 +157,7 @@ class Domain
|
||||
&& (int)$attributes['fields']['caneditdomain'] == 1
|
||||
&& (int)$attributes['fields']['letsencrypt'] == 0
|
||||
&& (int)$attributes['fields']['email_only'] == 0
|
||||
&& !$attributes['fields']['deactivated']
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -184,13 +190,11 @@ class Domain
|
||||
// specified certificate for domain
|
||||
if ($attributes['fields']['domain_hascert'] == 1) {
|
||||
$result['icon'] .= ' text-success';
|
||||
}
|
||||
// shared certificates (e.g. subdomain if domain where certificate is specified)
|
||||
} // shared certificates (e.g. subdomain of domain where certificate is specified)
|
||||
elseif ($attributes['fields']['domain_hascert'] == 2) {
|
||||
$result['icon'] .= ' text-warning';
|
||||
$result['title'] .= "\n" . lng('panel.ssleditor_infoshared');
|
||||
}
|
||||
// no certificate specified, using global fallbacks (IPs and Ports or if empty SSL settings)
|
||||
} // no certificate specified, using global fallbacks (IPs and Ports or if empty SSL settings)
|
||||
elseif ($attributes['fields']['domain_hascert'] == 0) {
|
||||
$result['icon'] .= ' text-danger';
|
||||
$result['title'] .= "\n" . lng('panel.ssleditor_infoglobal');
|
||||
@@ -212,4 +216,22 @@ class Domain
|
||||
}
|
||||
return lng('panel.empty');
|
||||
}
|
||||
|
||||
public static function getPhpConfigName(array $attributes): string
|
||||
{
|
||||
$sel_stmt = Database::prepare("SELECT `description` FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `id` = :id");
|
||||
$phpconfig = Database::pexecute_first($sel_stmt, ['id' => $attributes['data']]);
|
||||
if ((int)UI::getCurrentUser()['adminsession'] == 1) {
|
||||
$linker = UI::getLinker();
|
||||
$result = '<a href="' . $linker->getLink([
|
||||
'section' => 'phpsettings',
|
||||
'page' => 'overview',
|
||||
'searchfield' => 'c.id',
|
||||
'searchtext' => $attributes['data'],
|
||||
]) . '">' . $phpconfig['description'] . '</a>';
|
||||
} else {
|
||||
$result = $phpconfig['description'];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ class ProgressBar
|
||||
$skip_customer_traffic = false;
|
||||
try {
|
||||
$attributes['fields']['deactivated'] = 0;
|
||||
$result = Traffic::getCustomerStats($attributes['fields'], 'currentmonth');
|
||||
$result = Traffic::getCustomerStats($attributes['fields'], 'currentmonth', true);
|
||||
} catch (Exception $e) {
|
||||
if ($e->getCode() === 405) {
|
||||
$skip_customer_traffic = true;
|
||||
|
||||
@@ -47,4 +47,9 @@ class SSLCertificate
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function isNotLetsEncrypt(array $attributes): bool
|
||||
{
|
||||
return (int)$attributes['fields']['letsencrypt'] == 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,11 @@ class Style
|
||||
return $attributes['fields']['deactivated'] ? 'bg-danger' : '';
|
||||
}
|
||||
|
||||
public static function loginDisabled(array $attributes): string
|
||||
{
|
||||
return $attributes['fields']['login_enabled'] == 'N' ? 'bg-danger' : '';
|
||||
}
|
||||
|
||||
public static function resultIntegrityBad(array $attributes): string
|
||||
{
|
||||
return $attributes['fields']['result'] ? '' : 'bg-warning';
|
||||
@@ -60,17 +65,18 @@ class Style
|
||||
$today = time();
|
||||
$termination_css = 'bg-warning';
|
||||
if ($cdate < $today) {
|
||||
$termination_css = 'bg-danger';
|
||||
$termination_css = 'bg-danger text-light';
|
||||
}
|
||||
}
|
||||
return $attributes['fields']['deactivated'] ? 'bg-info' : $termination_css;
|
||||
$deactivated = $attributes['fields']['deactivated'] || $attributes['fields']['customer_deactivated'];
|
||||
return $deactivated ? 'bg-info text-light' : $termination_css;
|
||||
}
|
||||
|
||||
public static function resultCustomerLockedOrDeactivated(array $attributes): string
|
||||
{
|
||||
$row_css = '';
|
||||
if ((int)$attributes['fields']['deactivated'] == 1) {
|
||||
$row_css = 'bg-info';
|
||||
$row_css = 'bg-info text-light';
|
||||
} elseif (
|
||||
$attributes['fields']['loginfail_count'] >= Settings::Get('login.maxloginattempts')
|
||||
&& $attributes['fields']['lastlogin_fail'] > (time() - Settings::Get('login.deactivatetime'))
|
||||
|
||||
@@ -25,10 +25,14 @@
|
||||
|
||||
namespace Froxlor\UI\Callbacks;
|
||||
|
||||
use Froxlor\CurrentUser;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\Froxlor;
|
||||
use Froxlor\PhpHelper;
|
||||
use Froxlor\System\Markdown;
|
||||
use Froxlor\UI\Panel\UI;
|
||||
use Froxlor\User;
|
||||
use PDO;
|
||||
|
||||
class Text
|
||||
{
|
||||
@@ -40,6 +44,14 @@ class Text
|
||||
];
|
||||
}
|
||||
|
||||
public static function yesno(array $attributes): array
|
||||
{
|
||||
return [
|
||||
'macro' => 'boolean',
|
||||
'data' => $attributes['data'] == 'Y'
|
||||
];
|
||||
}
|
||||
|
||||
public static function customerfullname(array $attributes): string
|
||||
{
|
||||
return User::getCorrectFullUserDetails($attributes['fields'], true);
|
||||
@@ -82,7 +94,7 @@ class Text
|
||||
'entry' => $attributes['fields']['id'],
|
||||
'id' => 'cnModal' . $attributes['fields']['id'],
|
||||
'title' => lng('usersettings.custom_notes.title') . ': ' . ($attributes['fields']['loginname'] ?? $attributes['fields']['adminname']),
|
||||
'body' => nl2br($note)
|
||||
'body' => nl2br(Markdown::cleanCustomNotes($note))
|
||||
];
|
||||
}
|
||||
|
||||
@@ -105,4 +117,44 @@ class Text
|
||||
'body' => $body
|
||||
];
|
||||
}
|
||||
|
||||
public static function domainDuplicateModal(array $attributes): array
|
||||
{
|
||||
$linker = UI::getLinker();
|
||||
$result = $attributes['fields'];
|
||||
|
||||
$customers = [
|
||||
0 => lng('panel.please_choose')
|
||||
];
|
||||
$result_customers_stmt = Database::prepare("
|
||||
SELECT `customerid`, `loginname`, `name`, `firstname`, `company`
|
||||
FROM `" . TABLE_PANEL_CUSTOMERS . "` " . (CurrentUser::getField('customers_see_all') ? '' : " WHERE `adminid` = :adminid ") . "
|
||||
ORDER BY COALESCE(NULLIF(`name`,''), `company`) ASC
|
||||
");
|
||||
$params = [];
|
||||
if (CurrentUser::getField('customers_see_all') == '0') {
|
||||
$params['adminid'] = CurrentUser::getField('adminid');
|
||||
}
|
||||
Database::pexecute($result_customers_stmt, $params);
|
||||
|
||||
while ($row_customer = $result_customers_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$customers[$row_customer['customerid']] = User::getCorrectFullUserDetails($row_customer) . ' (' . $row_customer['loginname'] . ')';
|
||||
}
|
||||
|
||||
$domdup_data = include Froxlor::getInstallDir() . '/lib/formfields/admin/domains/formfield.domains_duplicate.php';
|
||||
|
||||
$body = UI::twig()->render(UI::validateThemeTemplate('/user/inline-form.html.twig'), [
|
||||
'formaction' => $linker->getLink(['section' => 'domains', 'page' => 'domains', 'action' => 'duplicate']),
|
||||
'formdata' => $domdup_data['domain_duplicate'],
|
||||
'editid' => $attributes['fields']['id'],
|
||||
'nosubmit' => 0
|
||||
]);
|
||||
return [
|
||||
'entry' => $attributes['fields']['id'],
|
||||
'id' => 'ddModal' . $attributes['fields']['id'],
|
||||
'title' => lng('admin.domain_duplicate_named', [$attributes['fields']['domain']]),
|
||||
'action' => 'duplicate',
|
||||
'body' => $body
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
namespace Froxlor\UI;
|
||||
|
||||
use Froxlor\CurrentUser;
|
||||
use Froxlor\FroxlorTwoFactorAuth;
|
||||
use Froxlor\Settings;
|
||||
use Froxlor\Validate\Check;
|
||||
|
||||
@@ -183,6 +185,21 @@ class Form
|
||||
}
|
||||
}
|
||||
|
||||
// OTP security validation for sensitive settings
|
||||
if (!Settings::Config('disable_otp_security_check') && isset($fielddata['required_otp']) && $do_show) {
|
||||
$otp_enabled_system = (bool)Settings::Get('2fa.enabled');
|
||||
$otp_enabled_user = (int)CurrentUser::getField('type_2fa') != 0;
|
||||
$do_show = !$fielddata['required_otp'] || ($otp_enabled_system && $otp_enabled_user);
|
||||
if (!$do_show) {
|
||||
$fielddata['note'] = lng('serversettings.option_requires_otp');
|
||||
if (!$otp_enabled_system) {
|
||||
$fielddata['note'] .= '<br>' . lng('2fa.2fa_not_activated');
|
||||
} elseif (!$otp_enabled_user) {
|
||||
$fielddata['note'] .= '<br>' . lng('2fa.2fa_not_activated_for_user');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$do_show) {
|
||||
$fielddata['visible'] = false;
|
||||
}
|
||||
@@ -232,7 +249,7 @@ class Form
|
||||
if (((isset($fielddetails['visible']) && $fielddetails['visible']) || !isset($fielddetails['visible'])) && (!$only_enabledisable || ($only_enabledisable && isset($fielddetails['overview_option'])))) {
|
||||
$newfieldvalue = self::getFormFieldData($fieldname, $fielddetails, $input);
|
||||
if ($newfieldvalue != $fielddetails['value']) {
|
||||
if (($error = \Froxlor\Validate\Form::validateFormField($fieldname, $fielddetails, $newfieldvalue)) != true) {
|
||||
if (($error = \Froxlor\Validate\Form::validateFormField($fieldname, $fielddetails, $newfieldvalue)) !== true) {
|
||||
Response::standardError($error, $fieldname);
|
||||
} else {
|
||||
$changed_fields[$fieldname] = $newfieldvalue;
|
||||
@@ -283,6 +300,38 @@ class Form
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!Settings::Config('disable_otp_security_check') && isset($fielddetails['required_otp']) && isset($changed_fields[$fieldname])) {
|
||||
$otp_enabled_system = (bool)Settings::Get('2fa.enabled');
|
||||
$otp_enabled_user = (int)CurrentUser::getField('type_2fa') != 0;
|
||||
$do_update = !$fielddetails['required_otp'] || ($otp_enabled_system && $otp_enabled_user);
|
||||
if ($do_update) {
|
||||
// setting that requires OTP verification
|
||||
if (empty($input['otp_verification'])) {
|
||||
// in case email 2fa is enabled, send it now
|
||||
CurrentUser::sendOtpEmail();
|
||||
// build up form
|
||||
if (is_array($url_params) && isset($url_params['filename'])) {
|
||||
$filename = $url_params['filename'];
|
||||
unset($url_params['filename']);
|
||||
} else {
|
||||
$filename = '';
|
||||
}
|
||||
HTML::askOTP('please_enter_otp', $filename, array_merge($url_params, $submitted_fields));
|
||||
} else {
|
||||
// validate given OTP code
|
||||
$code = trim($input['otp_verification']);
|
||||
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
|
||||
$result = $tfa->verifyCode(CurrentUser::getField('data_2fa'), $code, 3);
|
||||
if (!$result) {
|
||||
Response::standardError('otpnotvalidated');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// do not update this setting
|
||||
unset($changed_fields[$fieldname]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
namespace Froxlor\UI;
|
||||
|
||||
use Froxlor\Settings;
|
||||
|
||||
class HTML
|
||||
{
|
||||
|
||||
@@ -116,7 +118,7 @@ class HTML
|
||||
'label' => $navlabel,
|
||||
'icon' => $icon,
|
||||
'items' => $navigation_links,
|
||||
'active' => $box_active
|
||||
'active' => ((int)Settings::Get('panel.menu_collapsed') == 0 ? 1 : $box_active)
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -221,4 +223,17 @@ class HTML
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
|
||||
public static function askOTP(string $text, string $targetfile, array $params = [], string $replacer = '', array $back_link = [])
|
||||
{
|
||||
$text = lng('question.' . $text, [htmlspecialchars($replacer)]);
|
||||
|
||||
Panel\UI::view('form/otpquestion.html.twig', [
|
||||
'action' => $targetfile,
|
||||
'url_params' => $params,
|
||||
'question' => $text,
|
||||
'back_link' => $back_link
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,6 +230,7 @@ class Listing
|
||||
'label' => $coldata['label'],
|
||||
'checked' => in_array($column, $tabellisting['visible_columns']),
|
||||
'searchable' => $coldata['searchable'] ?? true,
|
||||
'isdefaultsearchfield' => $coldata['isdefaultsearchfield'] ?? false,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace Froxlor\UI\Panel;
|
||||
|
||||
use Froxlor\Idna\IdnaWrapper;
|
||||
use Froxlor\Settings;
|
||||
use Froxlor\System\Markdown;
|
||||
use Parsedown;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
@@ -53,9 +54,9 @@ class FroxlorTwig extends AbstractExtension
|
||||
$this,
|
||||
'idnDecodeFilter'
|
||||
]),
|
||||
new TwigFilter('parsedown', [
|
||||
new TwigFilter('markdown', [
|
||||
$this,
|
||||
'callParsedown'
|
||||
'callMarkdown'
|
||||
])
|
||||
];
|
||||
}
|
||||
@@ -148,10 +149,9 @@ class FroxlorTwig extends AbstractExtension
|
||||
return UI::getLinker()->getLink($linkopts);
|
||||
}
|
||||
|
||||
public function callParsedown($string)
|
||||
public function callMarkdown($string): string
|
||||
{
|
||||
$pd = new Parsedown();
|
||||
return $pd->line($string);
|
||||
return Markdown::cleanCustomNotes($string ?? "");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,7 +32,7 @@ class Response
|
||||
{
|
||||
|
||||
/**
|
||||
* Sends an header ( 'Location ...' ) to the browser.
|
||||
* Sends a header ( 'Location ...' ) to the browser.
|
||||
*
|
||||
* @param string $destination
|
||||
* Destination
|
||||
@@ -74,18 +74,18 @@ class Response
|
||||
$linker->filename = $path . $destination;
|
||||
}
|
||||
header('Location: ' . $linker->getLink());
|
||||
exit();
|
||||
exit;
|
||||
} elseif ($get_variables == null) {
|
||||
$linker = new Linker($destination);
|
||||
header('Location: ' . $linker->getLink());
|
||||
exit();
|
||||
exit;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints one ore more errormessages on screen
|
||||
* Prints one or more errormessages on screen
|
||||
*
|
||||
* @param array $errors
|
||||
* Errormessages
|
||||
@@ -93,8 +93,9 @@ class Response
|
||||
* A %s in the errormessage will be replaced by this string.
|
||||
* @param bool $throw_exception
|
||||
*
|
||||
* @author Florian Lippert <flo@syscp.org> (2003-2009)
|
||||
* @throws Exception
|
||||
* @author Ron Brand <ron.brand@web.de>
|
||||
* @author Florian Lippert <flo@syscp.org> (2003-2009)
|
||||
*/
|
||||
public static function standardError($errors = '', $replacer = '', $throw_exception = false)
|
||||
{
|
||||
@@ -115,7 +116,7 @@ class Response
|
||||
$error = '';
|
||||
foreach ($errors as $single_error) {
|
||||
if (strpos($single_error, ".") === false) {
|
||||
$single_error = 'error.'.$single_error;
|
||||
$single_error = 'error.' . $single_error;
|
||||
}
|
||||
$single_error = lng($single_error, [htmlentities($replacer)]);
|
||||
if (empty($error)) {
|
||||
@@ -157,7 +158,7 @@ class Response
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints one ore more errormessages on screen
|
||||
* Prints one or more errormessages on screen
|
||||
*
|
||||
* @param array $success_message
|
||||
* Errormessages
|
||||
@@ -166,12 +167,13 @@ class Response
|
||||
* @param array $params
|
||||
* @param bool $throw_exception
|
||||
*
|
||||
* @throws Exception
|
||||
* @author Florian Lippert <flo@syscp.org> (2003-2009)
|
||||
*/
|
||||
public static function standardSuccess($success_message = '', $replacer = '', $params = [], $throw_exception = false)
|
||||
{
|
||||
if (strpos($success_message, ".") === false) {
|
||||
$success_message = 'success.'.$success_message;
|
||||
$success_message = 'success.' . $success_message;
|
||||
}
|
||||
$success_message = lng($success_message, [htmlentities($replacer)]);
|
||||
|
||||
|
||||
@@ -314,4 +314,30 @@ class Check
|
||||
}
|
||||
return $returnvalue;
|
||||
}
|
||||
|
||||
public static function checkPgpPublicKeySetting($fieldname, $fielddata, $newfieldvalue, $allnewfieldvalues)
|
||||
{
|
||||
// if the field is empty, we don't need to check anything
|
||||
if ($newfieldvalue === '') {
|
||||
return [self::FORMFIELDS_PLAUSIBILITY_CHECK_OK];
|
||||
}
|
||||
|
||||
// check if gnupg extension is loaded
|
||||
if (!extension_loaded('gnupg')) {
|
||||
return [
|
||||
self::FORMFIELDS_PLAUSIBILITY_CHECK_ERROR,
|
||||
'gnupgextensionnotavailable'
|
||||
];
|
||||
}
|
||||
// check if the pgp public key is a valid key
|
||||
putenv('GNUPGHOME='.sys_get_temp_dir());
|
||||
if (gnupg_import(gnupg_init(), $newfieldvalue) === false) {
|
||||
return [
|
||||
self::FORMFIELDS_PLAUSIBILITY_CHECK_ERROR,
|
||||
'invalidpgppublickey'
|
||||
];
|
||||
}
|
||||
|
||||
return [self::FORMFIELDS_PLAUSIBILITY_CHECK_OK];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +319,11 @@ class Data
|
||||
{
|
||||
$returnvalue = 'stringformaterror';
|
||||
|
||||
if (preg_match('/^[^\0]*$/', $newfieldvalue)) {
|
||||
if (isset($fielddata['string_regexp']) && $fielddata['string_regexp'] != '') {
|
||||
if (preg_match($fielddata['string_regexp'], $newfieldvalue)) {
|
||||
$returnvalue = true;
|
||||
}
|
||||
} else if (preg_match('/^[^\0]*$/', $newfieldvalue)) {
|
||||
$returnvalue = true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user