* @license https://files.froxlor.org/misc/COPYING.txt GPLv2 */ namespace Froxlor\Api\Commands; use Exception; use Froxlor\Api\ApiCommand; use Froxlor\Api\ResourceEntity; use Froxlor\Cron\TaskId; use Froxlor\Database\Database; use Froxlor\Domain\Domain; use Froxlor\FileDir; use Froxlor\FroxlorLogger; use Froxlor\Idna\IdnaWrapper; use Froxlor\PhpHelper; use Froxlor\Settings; use Froxlor\System\Cronjob; use Froxlor\UI\Response; use Froxlor\User; use Froxlor\Validate\Validate; use PDO; /** * @since 0.10.0 */ class Domains extends ApiCommand implements ResourceEntity { /** * lists all domain entries * * @param bool $with_ips * optional, default true * @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 * @param int $sql_limit * optional specify number of results to be returned * @param int $sql_offset * optional specify offset for resultset * @param array $sql_orderby * optional array with index = fieldname and value = ASC|DESC to order the resultset by one or more * fields * * @access admin * @return string json-encoded array count|list * @throws Exception */ public function listing() { if ($this->isAdmin()) { $with_ips = $this->getParam('with_ips', true, true); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list domains"); $query_fields = []; $result_stmt = Database::prepare(" SELECT `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`) LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `ad` ON `d`.`aliasdomain`=`ad`.`id` WHERE `d`.`parentdomainid`='0' " . ($this->getUserDetail('customers_see_all') ? '' : " AND `d`.`adminid` = :adminid ") . $this->getSearchWhere($query_fields, true) . $this->getOrderBy() . $this->getLimit()); $params = []; if ($this->getUserDetail('customers_see_all') == '0') { $params['adminid'] = $this->getUserDetail('adminid'); } $params = array_merge($params, $query_fields); Database::pexecute($result_stmt, $params, true, true); $result = []; while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { $row['ipsandports'] = []; if ($with_ips) { $row['ipsandports'] = $this->getIpsForDomain($row['id']); } $row['domain_hascert'] = $this->getHasCertValueForDomain((int)$row['id'], (int)$row['parentdomainid']); $result[] = $row; } return $this->response([ 'count' => count($result), 'list' => $result ]); } throw new Exception("Not allowed to execute given command.", 403); } /** * get ips connected to given domain as array * * @param number $domain_id * @param bool $ssl_only * optional, return only ssl enabled ips, default false * @return array */ private function getIpsForDomain($domain_id = 0, $ssl_only = false) { $resultips_stmt = Database::prepare(" SELECT `ips`.* FROM `" . TABLE_DOMAINTOIP . "` AS `dti`, `" . TABLE_PANEL_IPSANDPORTS . "` AS `ips` WHERE `dti`.`id_ipandports` = `ips`.`id` AND `dti`.`id_domain` = :domainid " . ($ssl_only ? " AND `ips`.`ssl` = '1'" : "")); Database::pexecute($resultips_stmt, [ 'domainid' => $domain_id ]); $ipandports = []; while ($rowip = $resultips_stmt->fetch(PDO::FETCH_ASSOC)) { if (filter_var($rowip['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { $rowip['is_ipv6'] = true; } $ipandports[] = $rowip; } return $ipandports; } /** * returns the total number of accessible domains * * @access admin * @return string json-encoded array count|list * @throws Exception */ public function listingCount() { if ($this->isAdmin()) { $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] list domains"); $result_stmt = Database::prepare(" SELECT COUNT(*) as num_domains FROM `" . TABLE_PANEL_DOMAINS . "` `d` LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` `c` USING(`customerid`) LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `ad` ON `d`.`aliasdomain`=`ad`.`id` WHERE `d`.`parentdomainid`='0' " . ($this->getUserDetail('customers_see_all') ? '' : " AND `d`.`adminid` = :adminid ")); $params = []; if ($this->getUserDetail('customers_see_all') == '0') { $params['adminid'] = $this->getUserDetail('adminid'); } $result = Database::pexecute_first($result_stmt, $params, true, true); if ($result) { return $this->response($result['num_domains']); } } throw new Exception("Not allowed to execute given command.", 403); } /** * add new domain entry * * @param string $domain * domain-name * @param int $customerid * optional, required when called as admin (if $loginname is not specified) * @param string $loginname * optional, required when called as admin (if $customerid is not specified) * @param int $adminid * optional, default is the calling admin's ID * @param array $ipandport * optional list of ip/ports to assign to domain, default is system-default-ips * @param int $subcanemaildomain * optional, allow subdomains of this domain as email domains, 1 = choosable (default no), 2 = choosable * (default yes), 3 = always, default 0 (never) * @param bool $isemaildomain * optional, allow email usage with this domain, default 0 (false) * @param bool $email_only * optional, restrict domain to email usage, default 0 (false) * @param int $selectserveralias * optional, 0 = wildcard, 1 = www-alias, 2 = none, default [system.domaindefaultalias] * @param bool $speciallogfile * 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 string $registration_date * optional, date of domain registration in form of YYYY-MM-DD, default empty (none) * @param string $termination_date * optional, date of domain termination in form of YYYY-MM-DD, default empty (none) * @param bool $caneditdomain * optional, whether to allow the customer to edit domain settings, default 0 (false) * @param bool $isbinddomain * optional, whether to generate a dns-zone or not (only of nameserver is activated), default 0 (false) * @param string $zonefile * optional, custom dns zone filename (only of nameserver is activated), default empty (auto-generated) * @param bool $dkim * optional, currently not in use, default 0 (false) * @param string $specialsettings * optional, custom webserver vhost-content which is added to the generated vhost, default empty * @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 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 * optional, Enable writing an access-log file for this domain, default 1 (true) * @param bool $writeerrorlog * 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 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) * @param bool $openbasedir * optional, whether to activate openbasedir restriction for this domain, default 0 (false) * @param int $openbasedir_path * optional, either 0 for domains-docroot, 1 for customers-homedir or 2 for parent-directory of domains-docroot * @param int $phpsettingid * optional, specify php-configuration that is being used by id, default 1 (system-default) * @param int $mod_fcgid_starter * optional number of fcgid-starters if FCGID is used, default is -1 * @param int $mod_fcgid_maxrequests * optional number of fcgid-maxrequests if FCGID is used, default is -1 * @param bool $ssl_redirect * optional, whether to generate a https-redirect or not, default false; requires SSL to be enabled * @param bool $letsencrypt * optional, whether to generate a Let's Encrypt certificate for this domain, default false; requires * SSL to be enabled * @param array $ssl_ipandport * optional, list of ssl-enabled ip/port id's to assign to this domain, default empty * @param bool $dont_use_default_ssl_ipandport_if_empty * optional, do NOT set the systems default ssl ip addresses if none are given via $ssl_ipandport * parameter * @param bool $sslenabled * 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 * 0 (false) * @param int $hsts_maxage * optional max-age value for HSTS header * @param bool $hsts_sub * optional whether to add subdomains to the HSTS header * @param bool $hsts_preload * 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 * optional whether to honor the (server) cipher order for this domain. default 0 (false), requires SSL * @param bool $sessiontickets * optional whether to enable or disable TLS sessiontickets (RFC 5077) for this domain. default 1 * (true), requires SSL * @param bool $override_tls * 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 * if $override_tls is true, default empty or system.ssl_protocols setting if $override_tls is true * @param string $ssl_cipher_list * optional list of allowed/used ssl/tls ciphers, see system.ssl_cipher_list setting, only used/required * if $override_tls is true, default empty or system.ssl_cipher_list setting if $override_tls is true * @param string $tlsv13_cipher_list * optional list of allowed/used tls-1.3 specific ciphers, see system.tlsv13_cipher_list setting, only * used/required if $override_tls is true, default empty or system.tlsv13_cipher_list setting if * $override_tls is true * @param string $description * optional custom description (currently not used/shown in the frontend), default empty * * @access admin * @return string json-encoded array * @throws Exception */ public function add() { if ($this->isAdmin()) { if ($this->getUserDetail('domains_used') < $this->getUserDetail('domains') || $this->getUserDetail('domains') == '-1') { // parameters $p_domain = $this->getParam('domain'); // optional parameters $p_ipandports = $this->getParam('ipandport', true, explode(',', Settings::Get('system.defaultip'))); $adminid = intval($this->getParam('adminid', true, $this->getUserDetail('adminid'))); $subcanemaildomain = $this->getParam('subcanemaildomain', true, 0); $isemaildomain = $this->getBoolParam('isemaildomain', true, 0); $email_only = $this->getBoolParam('email_only', true, 0); $serveraliasoption = $this->getParam('selectserveralias', true, Settings::Get('system.domaindefaultalias')); $speciallogfile = $this->getBoolParam('speciallogfile', true, 0); $aliasdomain = intval($this->getParam('alias', true, 0)); $registration_date = $this->getParam('registration_date', true, ''); $termination_date = $this->getParam('termination_date', true, ''); $caneditdomain = $this->getBoolParam('caneditdomain', true, 0); $isbinddomain = $this->getBoolParam('isbinddomain', true, 0); $zonefile = $this->getParam('zonefile', true, ''); $dkim = $this->getBoolParam('dkim', true, 0); $specialsettings = $this->getParam('specialsettings', true, ''); $ssl_specialsettings = $this->getParam('ssl_specialsettings', true, ''); $include_specialsettings = $this->getBoolParam('include_specialsettings', true, 0); $notryfiles = $this->getBoolParam('notryfiles', true, 0); $writeaccesslog = $this->getBoolParam('writeaccesslog', true, 1); $writeerrorlog = $this->getBoolParam('writeerrorlog', true, 1); $documentroot = $this->getParam('documentroot', true, ''); $phpenabled = $this->getBoolParam('phpenabled', true, 0); $openbasedir = $this->getBoolParam('openbasedir', true, 0); $openbasedir_path = $this->getParam('openbasedir_path', true, 0); $phpsettingid = $this->getParam('phpsettingid', true, 1); $mod_fcgid_starter = $this->getParam('mod_fcgid_starter', true, -1); $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'))); $http2 = $this->getBoolParam('http2', true, 0); $hsts_maxage = $this->getParam('hsts_maxage', true, 0); $hsts_sub = $this->getBoolParam('hsts_sub', true, 0); $hsts_preload = $this->getBoolParam('hsts_preload', true, 0); $ocsp_stapling = $this->getBoolParam('ocsp_stapling', true, 0); $honorcipherorder = $this->getBoolParam('honorcipherorder', true, 0); $sessiontickets = $this->getBoolParam('sessiontickets', true, 1); $override_tls = $this->getBoolParam('override_tls', true, 0); $p_ssl_protocols = []; $ssl_cipher_list = ""; $tlsv13_cipher_list = ""; if ($this->getUserDetail('change_serversettings') == '1') { if ($override_tls) { $p_ssl_protocols = $this->getParam('ssl_protocols', true, explode(',', Settings::Get('system.ssl_protocols'))); $ssl_cipher_list = $this->getParam('ssl_cipher_list', true, Settings::Get('system.ssl_cipher_list')); $tlsv13_cipher_list = $this->getParam('tlsv13_cipher_list', true, Settings::Get('system.tlsv13_cipher_list')); } } $description = $this->getParam('description', true, ''); // validation $p_domain = strtolower($p_domain); if ($p_domain == strtolower(Settings::Get('system.hostname'))) { Response::standardError('admin_domain_emailsystemhostname', '', true); } if (substr($p_domain, 0, 4) == 'xn--') { Response::standardError('domain_nopunycode', '', true); } $idna_convert = new IdnaWrapper(); $domain = $idna_convert->encode(preg_replace([ '/\:(\d)+$/', '/^https?\:\/\//' ], '', Validate::validate($p_domain, 'domain'))); // Check whether domain validation is enabled and if, validate the domain if (Settings::Get('system.validate_domain') && !Validate::validateDomain($domain)) { Response::standardError([ 'stringiswrong', 'mydomain' ], '', true); } $customer = $this->getCustomerData(); $customerid = $customer['customerid']; if ($this->getUserDetail('customers_see_all') == '1' && $adminid != $this->getUserDetail('adminid')) { $admin_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_ADMINS . "` WHERE `adminid` = :adminid AND (`domains_used` < `domains` OR `domains` = '-1')"); $admin = Database::pexecute_first($admin_stmt, [ 'adminid' => $adminid ], true, true); if (empty($admin)) { Response::dynamicError("Selected admin cannot have any more domains or could not be found"); } unset($admin); } // set default path if admin/reseller has "change_serversettings == false" but we still // need to respect the documentroot_use_default_value - setting $path_suffix = ''; if (Settings::Get('system.documentroot_use_default_value') == 1) { $path_suffix = '/' . $domain; } $_documentroot = FileDir::makeCorrectDir($customer['documentroot'] . $path_suffix); $documentroot = Validate::validate($documentroot, 'documentroot', Validate::REGEX_DIR, '', [], true); // If path is empty and 'Use domain name as default value for DocumentRoot path' is enabled in settings, // set default path to subdomain or domain name if (!empty($documentroot)) { if (substr($documentroot, 0, 1) != '/' && !preg_match('/^https?\:\/\//', $documentroot)) { $documentroot = $_documentroot . '/' . $documentroot; } elseif (substr($documentroot, 0, 1) == '/' && $this->getUserDetail('change_serversettings') != '1') { Response::standardError('pathmustberelative', '', true); } } else { $documentroot = $_documentroot; } if (!is_null($registration_date)) { $registration_date = Validate::validate($registration_date, 'registration_date', Validate::REGEX_YYYY_MM_DD, '', [ '0000-00-00', '0', '' ], true); } if ($registration_date == '0000-00-00' || empty($registration_date)) { $registration_date = null; } if (!is_null($termination_date)) { $termination_date = Validate::validate($termination_date, 'termination_date', Validate::REGEX_YYYY_MM_DD, '', [ '0000-00-00', '0', '' ], true); } if ($termination_date == '0000-00-00' || empty($termination_date)) { $termination_date = null; } if ($this->getUserDetail('change_serversettings') == '1') { if (Settings::Get('system.bind_enable') == '1') { $zonefile = Validate::validate($zonefile, 'zonefile', '', '', [], true); } else { $isbinddomain = 0; $zonefile = ''; } $specialsettings = Validate::validate(str_replace("\r\n", "\n", $specialsettings), 'specialsettings', Validate::REGEX_CONF_TEXT, '', [], true); $ssl_protocols = []; if (!empty($p_ssl_protocols) && is_numeric($p_ssl_protocols)) { $p_ssl_protocols = [ $p_ssl_protocols ]; } if (!empty($p_ssl_protocols) && !is_array($p_ssl_protocols)) { $p_ssl_protocols = json_decode($p_ssl_protocols, true); } if (!empty($p_ssl_protocols) && is_array($p_ssl_protocols)) { $protocols_available = [ 'TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3' ]; foreach ($p_ssl_protocols as $ssl_protocol) { if (!in_array(trim($ssl_protocol), $protocols_available)) { $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_DEBUG, "[API] unknown SSL protocol '" . trim($ssl_protocol) . "'"); continue; } $ssl_protocols[] = $ssl_protocol; } } if (empty($ssl_protocols)) { $override_tls = '0'; } } else { $isbinddomain = '0'; if (Settings::Get('system.bind_enable') == '1') { $isbinddomain = '1'; } $caneditdomain = '1'; $zonefile = ''; $dkim = '0'; $specialsettings = ''; $ssl_specialsettings = ''; $include_specialsettings = 0; $notryfiles = '0'; $writeaccesslog = '1'; $writeerrorlog = '1'; $override_tls = '0'; $ssl_protocols = []; } if ($this->getUserDetail('caneditphpsettings') == '1' || $this->getUserDetail('change_serversettings') == '1') { if ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) { $phpsettingid_check_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `id` = :phpsettingid"); $phpsettingid_check = Database::pexecute_first($phpsettingid_check_stmt, [ 'phpsettingid' => $phpsettingid ], true, true); if (!isset($phpsettingid_check['id']) || $phpsettingid_check['id'] == '0' || $phpsettingid_check['id'] != $phpsettingid) { Response::standardError('phpsettingidwrong', '', true); } if ((int)Settings::Get('system.mod_fcgid') == 1) { $mod_fcgid_starter = Validate::validate($mod_fcgid_starter, 'mod_fcgid_starter', '/^[0-9]*$/', '', [ '-1', '' ], true); $mod_fcgid_maxrequests = Validate::validate($mod_fcgid_maxrequests, 'mod_fcgid_maxrequests', '/^[0-9]*$/', '', [ '-1', '' ], true); } else { $mod_fcgid_starter = '-1'; $mod_fcgid_maxrequests = '-1'; } } else { if ((int)Settings::Get('phpfpm.enabled') == 1) { $phpsettingid = Settings::Get('phpfpm.defaultini'); } else { $phpsettingid = Settings::Get('system.mod_fcgid_defaultini'); } $mod_fcgid_starter = '-1'; $mod_fcgid_maxrequests = '-1'; } } else { $phpenabled = '1'; $openbasedir = '1'; if ((int)Settings::Get('phpfpm.enabled') == 1) { $phpsettingid = Settings::Get('phpfpm.defaultini'); } else { $phpsettingid = Settings::Get('system.mod_fcgid_defaultini'); } $mod_fcgid_starter = '-1'; $mod_fcgid_maxrequests = '-1'; } if ($openbasedir_path > 2 && $openbasedir_path < 0) { $openbasedir_path = 0; } // check non-ssl IP $ipandports = $this->validateIpAddresses($p_ipandports); // check ssl IP $ssl_ipandports = []; if (Settings::Get('system.use_ssl') == "1" && !empty($p_ssl_ipandports)) { $ssl_ipandports = $this->validateIpAddresses($p_ssl_ipandports, true); if ($this->getUserDetail('change_serversettings') == '1') { $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; $http2 = 0; // we need this for the json_encode // if ssl is disabled or no ssl-ip/port exists $ssl_ipandports[] = -1; // HSTS $hsts_maxage = 0; $hsts_sub = 0; $hsts_preload = 0; // OCSP stapling $ocsp_stapling = 0; // vhost container settings $ssl_specialsettings = ''; $include_specialsettings = 0; } // validate dns if lets encrypt is enabled to check whether we can use it at all if ($letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') { $domain_ips = PhpHelper::gethostbynamel6($domain, true, Settings::Get('system.le_domain_dnscheck_resolver')); $selected_ips = $this->getIpsFromIdArray($ssl_ipandports); if ($domain_ips == false || count(array_intersect($selected_ips, $domain_ips)) <= 0) { Response::standardError('invaliddnsforletsencrypt', '', true); } } // We can't enable let's encrypt for wildcard-domains if ($serveraliasoption == '0' && $letsencrypt == '1') { Response::standardError('nowildcardwithletsencrypt', '', true); } // Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated if ($ssl_redirect > 0 && $letsencrypt == 1) { $ssl_redirect = 2; } if (!preg_match('/^https?\:\/\//', $documentroot)) { if (strstr($documentroot, ":") !== false) { Response::standardError('pathmaynotcontaincolon', '', true); } else { $documentroot = FileDir::makeCorrectDir($documentroot); } } $domain_check_stmt = Database::prepare(" SELECT `id`, `domain` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `domain` = :domain"); $domain_check = Database::pexecute_first($domain_check_stmt, [ 'domain' => strtolower($domain) ], true, true); $aliasdomain_check = [ 'id' => 0 ]; if ($aliasdomain != 0) { // Overwrite given ipandports with these of the "main" domain $ipandports = []; $ssl_ipandports = []; $origipresult_stmt = Database::prepare(" SELECT `id_ipandports` FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id"); Database::pexecute($origipresult_stmt, [ 'id' => $aliasdomain ], true, true); $ipdata_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :ipid"); while ($origip = $origipresult_stmt->fetch(PDO::FETCH_ASSOC)) { $_origip_tmp = Database::pexecute_first($ipdata_stmt, [ 'ipid' => $origip['id_ipandports'] ], true, true); if ($_origip_tmp['ssl'] == 0) { $ipandports[] = $origip['id_ipandports']; } else { $ssl_ipandports[] = $origip['id_ipandports']; } } if (count($ssl_ipandports) == 0) { // we need this for the json_encode // if ssl is disabled or no ssl-ip/port exists $ssl_ipandports[] = -1; } $aliasdomain_check_stmt = Database::prepare(" SELECT `d`.`id` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c` WHERE `d`.`customerid` = :customerid AND `d`.`aliasdomain` IS NULL AND `d`.`id` <> `c`.`standardsubdomain` AND `c`.`customerid` = :customerid AND `d`.`id` = :aliasdomainid"); $alias_params = [ 'customerid' => $customerid, 'aliasdomainid' => $aliasdomain ]; $aliasdomain_check = Database::pexecute_first($aliasdomain_check_stmt, $alias_params, true, true); } if (count($ipandports) == 0) { Response::standardError('noipportgiven', '', true); } if ($email_only == '1') { $isemaildomain = '1'; } else { $email_only = '0'; } if ($subcanemaildomain != '1' && $subcanemaildomain != '2' && $subcanemaildomain != '3') { $subcanemaildomain = '0'; } if ($serveraliasoption != '1' && $serveraliasoption != '2') { $serveraliasoption = '0'; } $idna_convert = new IdnaWrapper(); if ($domain == '') { Response::standardError([ 'stringisempty', 'mydomain' ], '', true); } elseif ($documentroot == '') { Response::standardError([ 'stringisempty', 'mydocumentroot' ], '', true); } elseif ($customerid == 0) { Response::standardError('adduserfirst', '', true); } elseif ($domain_check && strtolower($domain_check['domain']) == strtolower($domain)) { Response::standardError('domainalreadyexists', $idna_convert->decode($domain), true); } elseif ($aliasdomain_check && $aliasdomain_check['id'] != $aliasdomain) { Response::standardError('domainisaliasorothercustomer', '', true); } else { $wwwserveralias = ($serveraliasoption == '1') ? '1' : '0'; $iswildcarddomain = ($serveraliasoption == '0') ? '1' : '0'; $ins_data = [ 'domain' => $domain, 'domain_ace' => $idna_convert->decode($domain), 'customerid' => $customerid, 'adminid' => $adminid, 'documentroot' => $documentroot, 'aliasdomain' => ($aliasdomain != 0 ? $aliasdomain : null), 'zonefile' => $zonefile, 'dkim' => $dkim, 'wwwserveralias' => $wwwserveralias, 'iswildcarddomain' => $iswildcarddomain, 'isbinddomain' => $isbinddomain, 'isemaildomain' => $isemaildomain, 'email_only' => $email_only, 'subcanemaildomain' => $subcanemaildomain, 'caneditdomain' => $caneditdomain, 'phpenabled' => $phpenabled, 'openbasedir' => $openbasedir, 'openbasedir_path' => $openbasedir_path, 'speciallogfile' => $speciallogfile, 'specialsettings' => $specialsettings, 'ssl_specialsettings' => $ssl_specialsettings, 'include_specialsettings' => $include_specialsettings, 'notryfiles' => $notryfiles, 'writeaccesslog' => $writeaccesslog, 'writeerrorlog' => $writeerrorlog, 'ssl_redirect' => $ssl_redirect, 'add_date' => time(), 'registration_date' => $registration_date, 'termination_date' => $termination_date, 'phpsettingid' => $phpsettingid, 'mod_fcgid_starter' => $mod_fcgid_starter, 'mod_fcgid_maxrequests' => $mod_fcgid_maxrequests, 'letsencrypt' => $letsencrypt, 'http2' => $http2, 'hsts' => $hsts_maxage, 'hsts_sub' => $hsts_sub, 'hsts_preload' => $hsts_preload, 'ocsp_stapling' => $ocsp_stapling, 'override_tls' => $override_tls, 'ssl_protocols' => implode(",", $ssl_protocols), 'ssl_cipher_list' => $ssl_cipher_list, 'tlsv13_cipher_list' => $tlsv13_cipher_list, 'sslenabled' => $sslenabled, 'honorcipherorder' => $honorcipherorder, 'sessiontickets' => $sessiontickets, 'description' => $description ]; $ins_stmt = Database::prepare(" INSERT INTO `" . TABLE_PANEL_DOMAINS . "` SET `domain` = :domain, `domain_ace` = :domain_ace, `customerid` = :customerid, `adminid` = :adminid, `documentroot` = :documentroot, `aliasdomain` = :aliasdomain, `zonefile` = :zonefile, `dkim` = :dkim, `dkim_id` = '0', `dkim_privkey` = '', `dkim_pubkey` = '', `wwwserveralias` = :wwwserveralias, `iswildcarddomain` = :iswildcarddomain, `isbinddomain` = :isbinddomain, `isemaildomain` = :isemaildomain, `email_only` = :email_only, `subcanemaildomain` = :subcanemaildomain, `caneditdomain` = :caneditdomain, `phpenabled` = :phpenabled, `openbasedir` = :openbasedir, `openbasedir_path` = :openbasedir_path, `speciallogfile` = :speciallogfile, `specialsettings` = :specialsettings, `ssl_specialsettings` = :ssl_specialsettings, `include_specialsettings` = :include_specialsettings, `notryfiles` = :notryfiles, `writeaccesslog` = :writeaccesslog, `writeerrorlog` = :writeerrorlog, `ssl_redirect` = :ssl_redirect, `add_date` = :add_date, `registration_date` = :registration_date, `termination_date` = :termination_date, `phpsettingid` = :phpsettingid, `mod_fcgid_starter` = :mod_fcgid_starter, `mod_fcgid_maxrequests` = :mod_fcgid_maxrequests, `letsencrypt` = :letsencrypt, `http2` = :http2, `hsts` = :hsts, `hsts_sub` = :hsts_sub, `hsts_preload` = :hsts_preload, `ocsp_stapling` = :ocsp_stapling, `override_tls` = :override_tls, `ssl_protocols` = :ssl_protocols, `ssl_cipher_list` = :ssl_cipher_list, `tlsv13_cipher_list` = :tlsv13_cipher_list, `ssl_enabled` = :sslenabled, `ssl_honorcipherorder` = :honorcipherorder, `ssl_sessiontickets` = :sessiontickets, `description` = :description "); Database::pexecute($ins_stmt, $ins_data, true, true); $domainid = Database::lastInsertId(); $ins_data['id'] = $domainid; unset($ins_data); $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` + 1 WHERE `adminid` = :adminid"); Database::pexecute($upd_stmt, [ 'adminid' => $adminid ], true, true); $ins_stmt = Database::prepare(" INSERT INTO `" . TABLE_DOMAINTOIP . "` SET `id_domain` = :domainid, `id_ipandports` = :ipandportsid "); foreach ($ipandports as $ipportid) { $ins_data = [ 'domainid' => $domainid, 'ipandportsid' => $ipportid ]; Database::pexecute($ins_stmt, $ins_data, true, true); } foreach ($ssl_ipandports as $ssl_ipportid) { if ($ssl_ipportid > 0) { $ins_data = [ 'domainid' => $domainid, 'ipandportsid' => $ssl_ipportid ]; Database::pexecute($ins_stmt, $ins_data, true, true); } } Domain::triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger()); Cronjob::inserttask(TaskId::REBUILD_VHOST); // Using nameserver, insert a task which rebuilds the server config Cronjob::inserttask(TaskId::REBUILD_DNS); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] added domain '" . $domain . "'"); $result = $this->apiCall('Domains.get', [ 'domainname' => $domain ]); return $this->response($result); } } throw new Exception("No more resources available", 406); } throw new Exception("Not allowed to execute given command.", 403); } /** * return a domain entry by either id or domainname * * @param int $id * optional, the domain-id * @param string $domainname * optional, the domainname * @param bool $with_ips * optional, default true * @param bool $no_std_subdomain * optional, default false * * @access admin * @return string json-encoded array * @throws Exception */ public function get() { if ($this->isAdmin()) { $id = $this->getParam('id', true, 0); $dn_optional = $id > 0; $domainname = $this->getParam('domainname', $dn_optional, ''); $with_ips = $this->getParam('with_ips', true, true); $no_std_subdomain = $this->getParam('no_std_subdomain', true, false); // convert possible idn domain to punycode if (substr($domainname, 0, 4) != 'xn--') { $idna_convert = new IdnaWrapper(); $domainname = $idna_convert->encode($domainname); } $result_stmt = Database::prepare(" SELECT `d`.*, `c`.`customerid` FROM `" . TABLE_PANEL_DOMAINS . "` `d` LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` `c` USING(`customerid`) WHERE `d`.`parentdomainid` = '0' AND " . ($id > 0 ? "`d`.`id` = :iddn" : "`d`.`domain` = :iddn") . ($no_std_subdomain ? ' AND `d`.`id` <> `c`.`standardsubdomain`' : '') . ($this->getUserDetail('customers_see_all') ? '' : " AND `d`.`adminid` = :adminid")); $params = [ 'iddn' => ($id <= 0 ? $domainname : $id) ]; if ($this->getUserDetail('customers_see_all') == '0') { $params['adminid'] = $this->getUserDetail('adminid'); } $result = Database::pexecute_first($result_stmt, $params, true, true); if ($result) { $result['ipsandports'] = []; if ($with_ips) { $result['ipsandports'] = $this->getIpsForDomain($result['id']); } $result['domain_hascert'] = $this->getHasCertValueForDomain((int)$result['id'], (int)$result['parentdomainid']); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] get domain '" . $result['domain'] . "'"); return $this->response($result); } $key = ($id > 0 ? "id #" . $id : "domainname '" . $domainname . "'"); throw new Exception("Domain with " . $key . " could not be found", 404); } throw new Exception("Not allowed to execute given command.", 403); } private function getHasCertValueForDomain(int $domainid, int $parentdomainid): int { // nothing (ssl_global) $domain_hascert = 0; $ssl_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` WHERE `domainid` = :domainid"); Database::pexecute($ssl_stmt, array( "domainid" => $domainid )); $ssl_result = $ssl_stmt->fetch(PDO::FETCH_ASSOC); if (is_array($ssl_result) && isset($ssl_result['ssl_cert_file']) && $ssl_result['ssl_cert_file'] != '') { // own certificate (ssl_customer_green) $domain_hascert = 1; } else { // check if it's parent has one set (shared) if ($parentdomainid != 0) { $ssl_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` WHERE `domainid` = :domainid"); Database::pexecute($ssl_stmt, array( "domainid" => $parentdomainid )); $ssl_result = $ssl_stmt->fetch(PDO::FETCH_ASSOC); if (is_array($ssl_result) && isset($ssl_result['ssl_cert_file']) && $ssl_result['ssl_cert_file'] != '') { // parent has a certificate (ssl_shared) $domain_hascert = 2; } } } return $domain_hascert; } /** * validate given ips * * @param int|string|array $p_ipandports * @param boolean $ssl * default false * @param int $edit_id * default 0 * * @return array * @throws Exception */ private function validateIpAddresses($p_ipandports = null, $ssl = false, $edit_id = 0) { // when adding a new domain and no ip is given, we try to use the // system-default, check here if there is none // this is not required for ssl-enabled ip's if ($edit_id <= 0 && !$ssl && empty($p_ipandports)) { throw new Exception("No IPs given, unable to add domain (no default IPs set?)", 406); } // convert given value(s) correctly $ipandports = []; if (!empty($p_ipandports) && is_numeric($p_ipandports)) { $p_ipandports = [ $p_ipandports ]; } if (!empty($p_ipandports) && !is_array($p_ipandports)) { $p_ipandports = json_decode($p_ipandports, true); } // check whether there are ip usage restrictions $additional_ip_condition = ''; $aip_param = []; if ($this->getUserDetail('ip') != "-1") { // handle multiple-ip-array $additional_ip_condition = " AND `ip` IN (" . implode(",", json_decode($this->getUserDetail('ip'), true)) . ") "; } if (!empty($p_ipandports) && is_array($p_ipandports)) { $ipandport_check_stmt = Database::prepare(" SELECT `id`, `ip`, `port` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :ipandport " . ($ssl ? " AND `ssl` = '1'" : "") . $additional_ip_condition); foreach ($p_ipandports as $ipandport) { if (trim($ipandport) == "") { continue; } // fix if no ip/port is checked if (trim($ipandport) < 1) { continue; } $ipandport = intval($ipandport); $ip_params = array_merge([ 'ipandport' => $ipandport ], $aip_param); $ipandport_check = Database::pexecute_first($ipandport_check_stmt, $ip_params, true, true); if (!isset($ipandport_check['id']) || $ipandport_check['id'] == '0' || $ipandport_check['id'] != $ipandport) { Response::standardError('ipportdoesntexist', '', true); } else { $ipandports[] = $ipandport; } } } elseif ($edit_id > 0) { // set currently used ip's $ipsresult_stmt = Database::prepare(" SELECT d2i.`id_ipandports` FROM `" . TABLE_DOMAINTOIP . "` d2i LEFT JOIN `" . TABLE_PANEL_IPSANDPORTS . "` i ON i.id = d2i.id_ipandports WHERE d2i.`id_domain` = :id AND i.`ssl` = " . ($ssl ? "'1'" : "'0'")); Database::pexecute($ipsresult_stmt, [ 'id' => $edit_id ], true, true); while ($ipsresultrow = $ipsresult_stmt->fetch(PDO::FETCH_ASSOC)) { $ipandports[] = $ipsresultrow['id_ipandports']; } } return $ipandports; } /** * get ips from array of id's * * @param array $ips * @return array */ private function getIpsFromIdArray(array $ids) { $resultips_stmt = Database::prepare(" SELECT `ip` FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE id = :id "); $result = []; foreach ($ids as $id) { $entry = Database::pexecute_first($resultips_stmt, [ 'id' => $id ]); $result[] = $entry['ip']; } return $result; } /** * update domain entry by either id or domainname * * @param int $id * optional, the domain-id * @param string $domainname * optional, the domainname * @param int $customerid * required (if $loginname is not specified) * @param string $loginname * required (if $customerid is not specified) * @param int $adminid * optional, default is the calling admin's ID * @param array $ipandport * optional list of ip/ports to assign to domain, default is system-default-ips * @param int $subcanemaildomain * optional, allow subdomains of this domain as email domains, 1 = choosable (default no), 2 = choosable * (default yes), 3 = always, default 0 (never) * @param bool $isemaildomain * optional, allow email usage with this domain, default 0 (false) * @param bool $email_only * optional, restrict domain to email usage, default 0 (false) * @param int $selectserveralias * optional, 0 = wildcard, 1 = www-alias, 2 = none, default 0 * @param bool $speciallogfile * optional, whether to create an exclusive web-logfile for this domain, default 0 (false) * @param bool $speciallogverified * optional, when setting $speciallogfile to false, this needs to be set to true to confirm the action, * 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 string $registration_date * optional, date of domain registration in form of YYYY-MM-DD, default empty (none) * @param string $termination_date * optional, date of domain termination in form of YYYY-MM-DD, default empty (none) * @param bool $caneditdomain * optional, whether to allow the customer to edit domain settings, default 0 (false) * @param bool $isbinddomain * optional, whether to generate a dns-zone or not (only of nameserver is activated), default 0 (false) * @param string $zonefile * optional, custom dns zone filename (only of nameserver is activated), default empty (auto-generated) * @param bool $dkim * optional, currently not in use, default 0 (false) * @param string $specialsettings * optional, custom webserver vhost-content which is added to the generated vhost, default empty * @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 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 * @param bool $notryfiles * optional, [nginx only] do not generate the default try-files directive, default 0 (false) * @param bool $writeaccesslog * optional, Enable writing an access-log file for this domain, default 1 (true) * @param bool $writeerrorlog * 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 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) * @param bool $phpsettingsforsubdomains * optional, whether to apply php-setting to apply to all subdomains of this domain, default is read * from setting system.apply_phpconfigs_default * @param bool $openbasedir * optional, whether to activate openbasedir restriction for this domain, default 0 (false) * @param int $openbasedir_path * optional, either 0 for domains-docroot, 1 for customers-homedir or 2 for parent-directory of domains-docroot * @param int $phpsettingid * optional, specify php-configuration that is being used by id, default 1 (system-default) * @param int $mod_fcgid_starter * optional number of fcgid-starters if FCGID is used, default is -1 * @param int $mod_fcgid_maxrequests * optional number of fcgid-maxrequests if FCGID is used, default is -1 * @param bool $ssl_redirect * optional, whether to generate a https-redirect or not, default false; requires SSL to be enabled * @param bool $letsencrypt * optional, whether to generate a Let's Encrypt certificate for this domain, default false; requires * SSL to be enabled * @param array $ssl_ipandport * optional, list of ssl-enabled ip/port id's to assign to this domain, if left empty, the current set * value is being used, to remove all ssl ips use $remove_ssl_ipandport * @param bool $remove_ssl_ipandport * 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 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 * 0 (false) * @param int $hsts_maxage * optional max-age value for HSTS header * @param bool $hsts_sub * optional whether to add subdomains to the HSTS header * @param bool $hsts_preload * 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 * optional whether to honor the (server) cipher order for this domain. default 0 (false), requires SSL * @param bool $sessiontickets * optional whether to enable or disable TLS sessiontickets (RFC 5077) for this domain. default 1 * (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 * @throws Exception */ public function update() { if ($this->isAdmin()) { // parameters $id = $this->getParam('id', true, 0); $dn_optional = $id > 0; $domainname = $this->getParam('domainname', $dn_optional, ''); // get requested domain $result = $this->apiCall('Domains.get', [ 'id' => $id, 'domainname' => $domainname ]); $id = $result['id']; // optional parameters $p_ipandports = $this->getParam('ipandport', true, []); $adminid = intval($this->getParam('adminid', true, $result['adminid'])); if ($this->getParam('customerid', true, 0) == 0 && $this->getParam('loginname', true, '') == '') { $customerid = $result['customerid']; $customer = $this->apiCall('Customers.get', [ 'id' => $customerid ]); } else { $customer = $this->getCustomerData(); $customerid = $customer['customerid']; } $subcanemaildomain = $this->getParam('subcanemaildomain', true, $result['subcanemaildomain']); $isemaildomain = $this->getBoolParam('isemaildomain', true, $result['isemaildomain']); $email_only = $this->getBoolParam('email_only', true, $result['email_only']); $p_serveraliasoption = $this->getParam('selectserveralias', true, -1); $speciallogfile = $this->getBoolParam('speciallogfile', true, $result['speciallogfile']); $speciallogverified = $this->getBoolParam('speciallogverified', true, 0); $aliasdomain = intval($this->getParam('alias', true, $result['aliasdomain'])); $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']); $isbinddomain = $this->getBoolParam('isbinddomain', true, $result['isbinddomain']); $zonefile = $this->getParam('zonefile', true, $result['zonefile']); $dkim = $this->getBoolParam('dkim', true, $result['dkim']); $specialsettings = $this->getParam('specialsettings', true, $result['specialsettings']); $ssl_specialsettings = $this->getParam('ssl_specialsettings', true, $result['ssl_specialsettings']); $include_specialsettings = $this->getBoolParam('include_specialsettings', true, $result['include_specialsettings']); $ssfs = $this->getBoolParam('specialsettingsforsubdomains', true, Settings::Get('system.apply_specialsettings_default')); $notryfiles = $this->getBoolParam('notryfiles', true, $result['notryfiles']); $writeaccesslog = $this->getBoolParam('writeaccesslog', true, $result['writeaccesslog']); $writeerrorlog = $this->getBoolParam('writeerrorlog', true, $result['writeerrorlog']); $documentroot = $this->getParam('documentroot', true, $result['documentroot']); $phpenabled = $this->getBoolParam('phpenabled', true, $result['phpenabled']); $phpfs = $this->getBoolParam('phpsettingsforsubdomains', true, Settings::Get('system.apply_phpconfigs_default')); $openbasedir = $this->getBoolParam('openbasedir', true, $result['openbasedir']); $openbasedir_path = $this->getParam('openbasedir_path', true, $result['openbasedir_path']); $phpsettingid = $this->getParam('phpsettingid', true, $result['phpsettingid']); $mod_fcgid_starter = $this->getParam('mod_fcgid_starter', true, $result['mod_fcgid_starter']); $mod_fcgid_maxrequests = $this->getParam('mod_fcgid_maxrequests', true, $result['mod_fcgid_maxrequests']); $ssl_redirect = $this->getBoolParam('ssl_redirect', true, $result['ssl_redirect']); $letsencrypt = $this->getBoolParam('letsencrypt', true, $result['letsencrypt']); $remove_ssl_ipandport = $this->getBoolParam('remove_ssl_ipandport', true, 0); $p_ssl_ipandports = $this->getParam('ssl_ipandport', true, $remove_ssl_ipandport ? [ -1 ] : null); $sslenabled = $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']); $hsts_preload = $this->getBoolParam('hsts_preload', true, $result['hsts_preload']); $ocsp_stapling = $this->getBoolParam('ocsp_stapling', true, $result['ocsp_stapling']); $honorcipherorder = $this->getBoolParam('honorcipherorder', true, $result['ssl_honorcipherorder']); $sessiontickets = $this->getBoolParam('sessiontickets', true, $result['ssl_sessiontickets']); $override_tls = $this->getBoolParam('override_tls', true, $result['override_tls']); if ($this->getUserDetail('change_serversettings') == '1') { if ($override_tls) { $p_ssl_protocols = $this->getParam('ssl_protocols', true, explode(',', $result['ssl_protocols'])); $ssl_cipher_list = $this->getParam('ssl_cipher_list', true, $result['ssl_cipher_list']); $tlsv13_cipher_list = $this->getParam('tlsv13_cipher_list', true, $result['tlsv13_cipher_list']); } else { $p_ssl_protocols = []; $ssl_cipher_list = ""; $tlsv13_cipher_list = ""; } } else { $p_ssl_protocols = explode(',', $result['ssl_protocols']); $ssl_cipher_list = $result['ssl_cipher_list']; $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(" SELECT COUNT(`id`) AS count FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `parentdomainid` = :resultid "); $subdomains = Database::pexecute_first($subdomains_stmt, [ 'resultid' => $result['id'] ], true, true); $subdomains = $subdomains['count']; // count where this domain is alias domain $alias_check_stmt = Database::prepare(" SELECT COUNT(`id`) AS count FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `aliasdomain` = :resultid "); $alias_check = Database::pexecute_first($alias_check_stmt, [ 'resultid' => $result['id'] ], true, true); $alias_check = $alias_check['count']; // count where we are used in email-accounts $domain_emails_result_stmt = Database::prepare(" SELECT `email`, `email_full`, `destination`, `popaccountid` AS `number_email_forwarders` FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE `customerid` = :customerid AND `domainid` = :id "); Database::pexecute($domain_emails_result_stmt, [ 'customerid' => $result['customerid'], 'id' => $result['id'] ], true, true); $emails = Database::num_rows(); $email_forwarders = 0; $email_accounts = 0; while ($domain_emails_row = $domain_emails_result_stmt->fetch(PDO::FETCH_ASSOC)) { if ($domain_emails_row['destination'] != '') { $domain_emails_row['destination'] = explode(' ', FileDir::makeCorrectDestination($domain_emails_row['destination'])); $email_forwarders += count($domain_emails_row['destination']); if (in_array($domain_emails_row['email_full'], $domain_emails_row['destination'])) { $email_forwarders -= 1; $email_accounts++; } } } // handle change of customer (move domain from customer to customer) if ($customerid > 0 && $customerid != $result['customerid'] && Settings::Get('panel.allow_domain_change_customer') == '1') { // check whether target customer has enough resources $customer_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_CUSTOMERS . "` WHERE `customerid` = :customerid AND (`subdomains_used` + :subdomains <= `subdomains` OR `subdomains` = '-1' ) AND (`emails_used` + :emails <= `emails` OR `emails` = '-1' ) AND (`email_forwarders_used` + :forwarders <= `email_forwarders` OR `email_forwarders` = '-1' ) AND (`email_accounts_used` + :accounts <= `email_accounts` OR `email_accounts` = '-1' ) " . ($this->getUserDetail('customers_see_all') ? '' : " AND `adminid` = :adminid")); $params = [ 'customerid' => $customerid, 'subdomains' => $subdomains, 'emails' => $emails, 'forwarders' => $email_forwarders, 'accounts' => $email_accounts ]; if ($this->getUserDetail('customers_see_all') == '0') { $params['adminid'] = $this->getUserDetail('adminid'); } $customer = Database::pexecute_first($customer_stmt, $params, true, true); if (empty($customer) || $customer['customerid'] != $customerid) { Response::standardError('customerdoesntexist', '', true); } } // handle change of admin (move domain from admin to admin) if ($this->getUserDetail('customers_see_all') == '1') { if ($adminid > 0 && $adminid != $result['adminid'] && Settings::Get('panel.allow_domain_change_admin') == '1') { $admin_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_ADMINS . "` WHERE `adminid` = :adminid AND ( `domains_used` < `domains` OR `domains` = '-1' ) "); $admin = Database::pexecute_first($admin_stmt, [ 'adminid' => $adminid ], true, true); if (empty($admin) || $admin['adminid'] != $adminid) { Response::standardError('admindoesntexist', '', true); } } else { $adminid = $result['adminid']; } } else { $adminid = $result['adminid']; } if (!is_null($registration_date)) { $registration_date = Validate::validate($registration_date, 'registration_date', Validate::REGEX_YYYY_MM_DD, '', [ '0000-00-00', '0', '' ], true); } if ($registration_date == '0000-00-00' || empty($registration_date)) { $registration_date = null; } if (!is_null($termination_date)) { $termination_date = Validate::validate($termination_date, 'termination_date', Validate::REGEX_YYYY_MM_DD, '', [ '0000-00-00', '0', '' ], true); } if ($termination_date == '0000-00-00' || empty($termination_date)) { $termination_date = null; } $serveraliasoption = '2'; if ($result['iswildcarddomain'] == '1') { $serveraliasoption = '0'; } elseif ($result['wwwserveralias'] == '1') { $serveraliasoption = '1'; } if ($p_serveraliasoption > -1) { $serveraliasoption = $p_serveraliasoption; } $documentroot = Validate::validate($documentroot, 'documentroot', Validate::REGEX_DIR, '', [], true); if (!empty($documentroot) && $documentroot != $result['documentroot'] && substr($documentroot, 0, 1) == '/' && substr($documentroot, 0, strlen($customer['documentroot'])) != $customer['documentroot'] && $this->getUserDetail('change_serversettings') != '1') { Response::standardError('pathmustberelative', '', true); } // when moving customer and no path is specified, update would normally reuse the current document-root // which would point to the wrong customer, therefore we will re-create that directory if (!empty($documentroot) && $customerid > 0 && $customerid != $result['customerid'] && Settings::Get('panel.allow_domain_change_customer') == '1') { if (Settings::Get('system.documentroot_use_default_value') == 1) { $_documentroot = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $result['domain']); } else { $_documentroot = $customer['documentroot']; } // set the customers default docroot $documentroot = $_documentroot; } if ($documentroot == '') { // If path is empty and 'Use domain name as default value for DocumentRoot path' is enabled in settings, // set default path to subdomain or domain name if (Settings::Get('system.documentroot_use_default_value') == 1) { $documentroot = FileDir::makeCorrectDir($customer['documentroot'] . '/' . $result['domain']); } else { $documentroot = $customer['documentroot']; } } if (!preg_match('/^https?\:\/\//', $documentroot) && strstr($documentroot, ":") !== false) { Response::standardError('pathmaynotcontaincolon', '', true); } if ($this->getUserDetail('change_serversettings') == '1') { if (Settings::Get('system.bind_enable') == '1') { $zonefile = Validate::validate($zonefile, 'zonefile', '', '', [], true); } else { $isbinddomain = $result['isbinddomain']; $zonefile = $result['zonefile']; } if (Settings::Get('dkim.use_dkim') != '1') { $dkim = $result['dkim']; } $specialsettings = Validate::validate(str_replace("\r\n", "\n", $specialsettings), 'specialsettings', Validate::REGEX_CONF_TEXT, '', [], true); $ssl_protocols = []; if (!empty($p_ssl_protocols) && is_numeric($p_ssl_protocols)) { $p_ssl_protocols = [ $p_ssl_protocols ]; } if (!empty($p_ssl_protocols) && !is_array($p_ssl_protocols)) { $p_ssl_protocols = json_decode($p_ssl_protocols, true); } if (!empty($p_ssl_protocols) && is_array($p_ssl_protocols)) { $protocols_available = [ 'TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3' ]; foreach ($p_ssl_protocols as $ssl_protocol) { if (!in_array(trim($ssl_protocol), $protocols_available)) { $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_DEBUG, "[API] unknown SSL protocol '" . trim($ssl_protocol) . "'"); continue; } $ssl_protocols[] = $ssl_protocol; } } if (empty($ssl_protocols)) { $override_tls = '0'; } } else { $isbinddomain = $result['isbinddomain']; $zonefile = $result['zonefile']; $dkim = $result['dkim']; $specialsettings = $result['specialsettings']; $ssl_specialsettings = $result['ssl_specialsettings']; $include_specialsettings = $result['include_specialsettings']; $ssfs = (empty($specialsettings) ? 0 : 1); $notryfiles = $result['notryfiles']; $writeaccesslog = $result['writeaccesslog']; $writeerrorlog = $result['writeerrorlog']; $ssl_protocols = $p_ssl_protocols; $override_tls = $result['override_tls']; } if ($this->getUserDetail('caneditphpsettings') == '1' || $this->getUserDetail('change_serversettings') == '1') { if ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) { $phpsettingid_check_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `id` = :phpid "); $phpsettingid_check = Database::pexecute_first($phpsettingid_check_stmt, [ 'phpid' => $phpsettingid ], true, true); if (!isset($phpsettingid_check['id']) || $phpsettingid_check['id'] == '0' || $phpsettingid_check['id'] != $phpsettingid) { Response::standardError('phpsettingidwrong', '', true); } if ((int)Settings::Get('system.mod_fcgid') == 1) { $mod_fcgid_starter = Validate::validate($mod_fcgid_starter, 'mod_fcgid_starter', '/^[0-9]*$/', '', [ '-1', '' ], true); $mod_fcgid_maxrequests = Validate::validate($mod_fcgid_maxrequests, 'mod_fcgid_maxrequests', '/^[0-9]*$/', '', [ '-1', '' ], true); } else { $mod_fcgid_starter = $result['mod_fcgid_starter']; $mod_fcgid_maxrequests = $result['mod_fcgid_maxrequests']; } } else { $phpsettingid = $result['phpsettingid']; $phpfs = 1; $mod_fcgid_starter = $result['mod_fcgid_starter']; $mod_fcgid_maxrequests = $result['mod_fcgid_maxrequests']; } } else { $phpenabled = $result['phpenabled']; $openbasedir = $result['openbasedir']; $phpsettingid = $result['phpsettingid']; $phpfs = 1; $mod_fcgid_starter = $result['mod_fcgid_starter']; $mod_fcgid_maxrequests = $result['mod_fcgid_maxrequests']; } // check changes of openbasedir-path variable if ($openbasedir_path > 2 && $openbasedir_path < 0) { $openbasedir_path = 0; } // check non-ssl IP $ipandports = $this->validateIpAddresses($p_ipandports, false, $result['id']); // check ssl IP if (empty($p_ssl_ipandports) || (!is_array($p_ssl_ipandports) && is_null($p_ssl_ipandports))) { $p_ssl_ipandports = []; foreach ($result['ipsandports'] as $ip) { if ($ip['ssl'] == 1) { $p_ssl_ipandports[] = $ip['id']; } } } $ssl_ipandports = []; if (Settings::Get('system.use_ssl') == "1" && !empty($p_ssl_ipandports) && $p_ssl_ipandports[0] != -1) { $ssl_ipandports = $this->validateIpAddresses($p_ssl_ipandports, true, $result['id']); if ($this->getUserDetail('change_serversettings') == '1') { $ssl_specialsettings = Validate::validate(str_replace("\r\n", "\n", $ssl_specialsettings), 'ssl_specialsettings', '/^[^\0]*$/', '', [], true); } } 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; $http2 = 0; // we need this for the json_encode // if ssl is disabled or no ssl-ip/port exists $ssl_ipandports[] = -1; // HSTS $hsts_maxage = 0; $hsts_sub = 0; $hsts_preload = 0; // OCSP stapling $ocsp_stapling = 0; // vhost container settings $ssl_specialsettings = ''; $include_specialsettings = 0; } // validate dns if lets encrypt is enabled to check whether we can use it at all if ($letsencrypt == '1' && Settings::Get('system.le_domain_dnscheck') == '1') { $domain_ips = PhpHelper::gethostbynamel6($result['domain'], true, Settings::Get('system.le_domain_dnscheck_resolver')); $selected_ips = $this->getIpsFromIdArray($ssl_ipandports); if ($domain_ips == false || count(array_intersect($selected_ips, $domain_ips)) <= 0) { Response::standardError('invaliddnsforletsencrypt', '', true); } } // We can't enable let's encrypt for wildcard-domains if ($serveraliasoption == '0' && $letsencrypt == '1') { Response::standardError('nowildcardwithletsencrypt', '', true); } // Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated if (($result['letsencrypt'] != $letsencrypt || $result['ssl_redirect'] != $ssl_redirect) && $ssl_redirect > 0 && $letsencrypt == 1) { $ssl_redirect = 2; } if (!preg_match('/^https?\:\/\//', $documentroot)) { if ($documentroot != $result['documentroot']) { if (substr($documentroot, 0, 1) != "/") { $documentroot = $customer['documentroot'] . '/' . $documentroot; } $documentroot = FileDir::makeCorrectDir($documentroot); } } if ($email_only == '1') { $isemaildomain = '1'; } else { $email_only = '0'; } if ($subcanemaildomain != '1' && $subcanemaildomain != '2' && $subcanemaildomain != '3') { $subcanemaildomain = '0'; } $aliasdomain_check = [ 'id' => 0 ]; if ($aliasdomain != 0) { // Overwrite given ipandports with these of the "main" domain $ipandports = []; $ssl_ipandports = []; $origipresult_stmt = Database::prepare(" SELECT `id_ipandports` FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :aliasdomain "); Database::pexecute($origipresult_stmt, [ 'aliasdomain' => $aliasdomain ], true, true); $ipdata_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_IPSANDPORTS . "` WHERE `id` = :ipid"); while ($origip = $origipresult_stmt->fetch(PDO::FETCH_ASSOC)) { $_origip_tmp = Database::pexecute_first($ipdata_stmt, [ 'ipid' => $origip['id_ipandports'] ], true, true); if ($_origip_tmp['ssl'] == 0) { $ipandports[] = $origip['id_ipandports']; } else { $ssl_ipandports[] = $origip['id_ipandports']; } } if (count($ssl_ipandports) == 0) { // we need this for the json_encode // if ssl is disabled or no ssl-ip/port exists $ssl_ipandports[] = -1; } $aliasdomain_check_stmt = Database::prepare(" SELECT `d`.`id` FROM `" . TABLE_PANEL_DOMAINS . "` `d`, `" . TABLE_PANEL_CUSTOMERS . "` `c` WHERE `d`.`customerid` = :customerid AND `d`.`aliasdomain` IS NULL AND `d`.`id` <> `c`.`standardsubdomain` AND `c`.`customerid` = :customerid AND `d`.`id` = :aliasdomain "); $aliasdomain_check = Database::pexecute_first($aliasdomain_check_stmt, [ 'customerid' => $customerid, 'aliasdomain' => $aliasdomain ], true, true); } if (count($ipandports) == 0) { Response::standardError('noipportgiven', '', true); } if ($aliasdomain_check['id'] != $aliasdomain) { Response::standardError('domainisaliasorothercustomer', '', true); } if ($serveraliasoption != '1' && $serveraliasoption != '2') { $serveraliasoption = '0'; } $wwwserveralias = ($serveraliasoption == '1') ? '1' : '0'; $iswildcarddomain = ($serveraliasoption == '0') ? '1' : '0'; if ($documentroot != $result['documentroot'] || $ssl_redirect != $result['ssl_redirect'] || $wwwserveralias != $result['wwwserveralias'] || $iswildcarddomain != $result['iswildcarddomain'] || $phpenabled != $result['phpenabled'] || $openbasedir != $result['openbasedir'] || $phpsettingid != $result['phpsettingid'] || $mod_fcgid_starter != $result['mod_fcgid_starter'] || $mod_fcgid_maxrequests != $result['mod_fcgid_maxrequests'] || $specialsettings != $result['specialsettings'] || $ssl_specialsettings != $result['ssl_specialsettings'] || $notryfiles != $result['notryfiles'] || $writeaccesslog != $result['writeaccesslog'] || $writeerrorlog != $result['writeerrorlog'] || $aliasdomain != $result['aliasdomain'] || $email_only != $result['email_only'] || ($speciallogfile != $result['speciallogfile'] && $speciallogverified == '1') || $letsencrypt != $result['letsencrypt'] || $http2 != $result['http2'] || $hsts_maxage != $result['hsts'] || $hsts_sub != $result['hsts_sub'] || $hsts_preload != $result['hsts_preload'] || $ocsp_stapling != $result['ocsp_stapling'] ) { Cronjob::inserttask(TaskId::REBUILD_VHOST); } if ($speciallogfile != $result['speciallogfile'] && $speciallogverified != '1') { $speciallogfile = $result['speciallogfile']; } if ($isbinddomain != $result['isbinddomain'] || $zonefile != $result['zonefile'] || $dkim != $result['dkim'] || $isemaildomain != $result['isemaildomain']) { Cronjob::inserttask(TaskId::REBUILD_DNS); } // check whether nameserver has been disabled, #581 if ($isbinddomain != $result['isbinddomain'] && $isbinddomain == 0) { Cronjob::inserttask(TaskId::DELETE_DOMAIN_PDNS, $result['domain']); } if ($isemaildomain == '0' && $result['isemaildomain'] == '1') { $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_MAIL_USERS . "` WHERE `domainid` = :id "); Database::pexecute($del_stmt, [ 'id' => $id ], true, true); $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE `domainid` = :id "); Database::pexecute($del_stmt, [ 'id' => $id ], true, true); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] deleted domain #" . $id . " from mail-tables as is-email-domain was set to 0"); } // check whether LE has been disabled, so we remove the certificate if ($letsencrypt == '0' && $result['letsencrypt'] == '1') { $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` WHERE `domainid` = :id "); Database::pexecute($del_stmt, [ 'id' => $id ], true, true); // remove domain from acme.sh / lets encrypt if used Cronjob::inserttask(TaskId::DELETE_DOMAIN_SSL, $result['domain']); } $updatechildren = ''; if ($subcanemaildomain == '0' && $result['subcanemaildomain'] != '0') { $updatechildren = ", `isemaildomain` = '0' "; } elseif ($subcanemaildomain == '3' && $result['subcanemaildomain'] != '3') { $updatechildren = ", `isemaildomain` = '1' "; } if ($customerid != $result['customerid'] && Settings::Get('panel.allow_domain_change_customer') == '1') { $upd_data = [ 'customerid' => $customerid, 'domainid' => $result['id'] ]; $upd_stmt = Database::prepare(" UPDATE `" . TABLE_MAIL_USERS . "` SET `customerid` = :customerid WHERE `domainid` = :domainid "); Database::pexecute($upd_stmt, $upd_data, true, true); $upd_stmt = Database::prepare(" UPDATE `" . TABLE_MAIL_VIRTUAL . "` SET `customerid` = :customerid WHERE `domainid` = :domainid "); Database::pexecute($upd_stmt, $upd_data, true, true); $upd_data = [ 'subdomains' => $subdomains, 'emails' => $emails, 'forwarders' => $email_forwarders, 'accounts' => $email_accounts ]; $upd_data['customerid'] = $customerid; $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `subdomains_used` = `subdomains_used` + :subdomains, `emails_used` = `emails_used` + :emails, `email_forwarders_used` = `email_forwarders_used` + :forwarders, `email_accounts_used` = `email_accounts_used` + :accounts WHERE `customerid` = :customerid "); Database::pexecute($upd_stmt, $upd_data, true, true); $upd_data['customerid'] = $result['customerid']; $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `subdomains_used` = `subdomains_used` - :subdomains, `emails_used` = `emails_used` - :emails, `email_forwarders_used` = `email_forwarders_used` - :forwarders, `email_accounts_used` = `email_accounts_used` - :accounts WHERE `customerid` = :customerid "); Database::pexecute($upd_stmt, $upd_data, true, true); } if ($adminid != $result['adminid'] && Settings::Get('panel.allow_domain_change_admin') == '1') { $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` + 1 WHERE `adminid` = :adminid "); Database::pexecute($upd_stmt, [ 'adminid' => $adminid ], true, true); $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` - 1 WHERE `adminid` = :adminid "); Database::pexecute($upd_stmt, [ 'adminid' => $result['adminid'] ], true, true); } $_update_data = []; if ($ssfs == 1) { $_update_data['specialsettings'] = $specialsettings; $_update_data['ssl_specialsettings'] = $ssl_specialsettings; $_update_data['include_specialsettings'] = $include_specialsettings; $upd_specialsettings = ", `specialsettings` = :specialsettings, `ssl_specialsettings` = :ssl_specialsettings, `include_specialsettings` = :include_specialsettings "; } else { $upd_specialsettings = ''; $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `specialsettings`='', `ssl_specialsettings`='', `include_specialsettings`='0' WHERE `parentdomainid` = :id "); Database::pexecute($upd_stmt, [ 'id' => $id ], true, true); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] removed specialsettings on all subdomains of domain #" . $id); } $wwwserveralias = ($serveraliasoption == '1') ? '1' : '0'; $iswildcarddomain = ($serveraliasoption == '0') ? '1' : '0'; $update_data = []; $update_data['customerid'] = $customerid; $update_data['adminid'] = $adminid; $update_data['documentroot'] = $documentroot; $update_data['ssl_redirect'] = $ssl_redirect; $update_data['aliasdomain'] = ($aliasdomain != 0 && $alias_check == 0) ? $aliasdomain : null; $update_data['isbinddomain'] = $isbinddomain; $update_data['isemaildomain'] = $isemaildomain; $update_data['email_only'] = $email_only; $update_data['subcanemaildomain'] = $subcanemaildomain; $update_data['dkim'] = $dkim; $update_data['caneditdomain'] = $caneditdomain; $update_data['zonefile'] = $zonefile; $update_data['wwwserveralias'] = $wwwserveralias; $update_data['iswildcarddomain'] = $iswildcarddomain; $update_data['phpenabled'] = $phpenabled; $update_data['openbasedir'] = $openbasedir;; $update_data['openbasedir_path'] = $openbasedir_path; $update_data['speciallogfile'] = $speciallogfile; $update_data['phpsettingid'] = $phpsettingid; $update_data['mod_fcgid_starter'] = $mod_fcgid_starter; $update_data['mod_fcgid_maxrequests'] = $mod_fcgid_maxrequests; $update_data['specialsettings'] = $specialsettings; $update_data['ssl_specialsettings'] = $ssl_specialsettings; $update_data['include_specialsettings'] = $include_specialsettings; $update_data['notryfiles'] = $notryfiles; $update_data['writeaccesslog'] = $writeaccesslog; $update_data['writeerrorlog'] = $writeerrorlog; $update_data['registration_date'] = $registration_date; $update_data['termination_date'] = $termination_date; $update_data['letsencrypt'] = $letsencrypt; $update_data['http2'] = $http2; $update_data['hsts'] = $hsts_maxage; $update_data['hsts_sub'] = $hsts_sub; $update_data['hsts_preload'] = $hsts_preload; $update_data['ocsp_stapling'] = $ocsp_stapling; $update_data['override_tls'] = $override_tls; $update_data['ssl_protocols'] = implode(",", $ssl_protocols); $update_data['ssl_cipher_list'] = $ssl_cipher_list; $update_data['tlsv13_cipher_list'] = $tlsv13_cipher_list; $update_data['sslenabled'] = $sslenabled; $update_data['honorcipherorder'] = $honorcipherorder; $update_data['sessiontickets'] = $sessiontickets; $update_data['description'] = $description; $update_data['deactivated'] = $deactivated; $update_data['id'] = $id; $update_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `customerid` = :customerid, `adminid` = :adminid, `documentroot` = :documentroot, `ssl_redirect` = :ssl_redirect, `aliasdomain` = :aliasdomain, `isbinddomain` = :isbinddomain, `isemaildomain` = :isemaildomain, `email_only` = :email_only, `subcanemaildomain` = :subcanemaildomain, `dkim` = :dkim, `caneditdomain` = :caneditdomain, `zonefile` = :zonefile, `wwwserveralias` = :wwwserveralias, `iswildcarddomain` = :iswildcarddomain, `phpenabled` = :phpenabled, `openbasedir` = :openbasedir, `openbasedir_path` = :openbasedir_path, `speciallogfile` = :speciallogfile, `phpsettingid` = :phpsettingid, `mod_fcgid_starter` = :mod_fcgid_starter, `mod_fcgid_maxrequests` = :mod_fcgid_maxrequests, `specialsettings` = :specialsettings, `ssl_specialsettings` = :ssl_specialsettings, `include_specialsettings` = :include_specialsettings, `notryfiles` = :notryfiles, `writeaccesslog` = :writeaccesslog, `writeerrorlog` = :writeerrorlog, `registration_date` = :registration_date, `termination_date` = :termination_date, `letsencrypt` = :letsencrypt, `http2` = :http2, `hsts` = :hsts, `hsts_sub` = :hsts_sub, `hsts_preload` = :hsts_preload, `ocsp_stapling` = :ocsp_stapling, `override_tls` = :override_tls, `ssl_protocols` = :ssl_protocols, `ssl_cipher_list` = :ssl_cipher_list, `tlsv13_cipher_list` = :tlsv13_cipher_list, `ssl_enabled` = :sslenabled, `ssl_honorcipherorder` = :honorcipherorder, `ssl_sessiontickets` = :sessiontickets, `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; $_update_data['openbasedir'] = $openbasedir; $_update_data['openbasedir_path'] = $openbasedir_path; $_update_data['mod_fcgid_starter'] = $mod_fcgid_starter; $_update_data['mod_fcgid_maxrequests'] = $mod_fcgid_maxrequests; $_update_data['notryfiles'] = $notryfiles; $_update_data['writeaccesslog'] = $writeaccesslog; $_update_data['writeerrorlog'] = $writeerrorlog; $_update_data['override_tls'] = $override_tls; $_update_data['ssl_protocols'] = implode(",", $ssl_protocols); $_update_data['ssl_cipher_list'] = $ssl_cipher_list; $_update_data['tlsv13_cipher_list'] = $tlsv13_cipher_list; $_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 = ''; if ($phpfs == 1) { $_update_data['phpsettingid'] = $phpsettingid; $update_phpconfig = ", `phpsettingid` = :phpsettingid"; } // if we have no more ssl-ip's for this domain, // all its subdomains must have "ssl-redirect = 0" // and disable let's encrypt $update_sslredirect = ''; if (count($ssl_ipandports) == 1 && $ssl_ipandports[0] == -1) { $update_sslredirect = ", `ssl_redirect` = '0', `letsencrypt` = '0' "; } $_update_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `customerid` = :customerid, `adminid` = :adminid, `phpenabled` = :phpenabled, `openbasedir` = :openbasedir, `openbasedir_path` = :openbasedir_path, `mod_fcgid_starter` = :mod_fcgid_starter, `mod_fcgid_maxrequests` = :mod_fcgid_maxrequests, `notryfiles` = :notryfiles, `writeaccesslog` = :writeaccesslog, `writeerrorlog` = :writeerrorlog, `override_tls` = :override_tls, `ssl_protocols` = :ssl_protocols, `ssl_cipher_list` = :ssl_cipher_list, `tlsv13_cipher_list` = :tlsv13_cipher_list, `ssl_honorcipherorder` = :honorcipherorder, `ssl_sessiontickets` = :sessiontickets, `deactivated` = :deactivated " . $update_phpconfig . $upd_specialsettings . $updatechildren . $update_sslredirect . " WHERE `parentdomainid` = :parentdomainid "); Database::pexecute($_update_stmt, $_update_data, true, true); // get current ip<>domain entries $ip_sel_stmt = Database::prepare(" SELECT id_ipandports FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id "); Database::pexecute($ip_sel_stmt, [ 'id' => $id ], true, true); $current_ips = []; while ($cIP = $ip_sel_stmt->fetch(\PDO::FETCH_ASSOC)) { $current_ips[] = $cIP['id_ipandports']; } // Cleanup domain <-> ip mapping $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id "); Database::pexecute($del_stmt, [ 'id' => $id ], true, true); $ins_stmt = Database::prepare(" INSERT INTO `" . TABLE_DOMAINTOIP . "` SET `id_domain` = :domainid, `id_ipandports` = :ipportid "); foreach ($ipandports as $ipportid) { Database::pexecute($ins_stmt, [ 'domainid' => $id, 'ipportid' => $ipportid ], true, true); } foreach ($ssl_ipandports as $ssl_ipportid) { if ($ssl_ipportid > 0) { Database::pexecute($ins_stmt, [ 'domainid' => $id, 'ipportid' => $ssl_ipportid ], true, true); } } // check ip changes $all_new_ips = array_merge($ipandports, $ssl_ipandports); if (count(array_diff($current_ips, $all_new_ips)) != 0 || count(array_diff($all_new_ips, $current_ips)) != 0) { Cronjob::inserttask(TaskId::REBUILD_VHOST); } // Cleanup domain <-> ip mapping for subdomains $domainidsresult_stmt = Database::prepare(" SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `parentdomainid` = :id "); Database::pexecute($domainidsresult_stmt, [ 'id' => $id ], true, true); while ($row = $domainidsresult_stmt->fetch(PDO::FETCH_ASSOC)) { $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :rowid "); Database::pexecute($del_stmt, [ 'rowid' => $row['id'] ], true, true); $ins_stmt = Database::prepare(" INSERT INTO `" . TABLE_DOMAINTOIP . "` SET `id_domain` = :rowid, `id_ipandports` = :ipportid "); foreach ($ipandports as $ipportid) { Database::pexecute($ins_stmt, [ 'rowid' => $row['id'], 'ipportid' => $ipportid ], true, true); } foreach ($ssl_ipandports as $ssl_ipportid) { if ($ssl_ipportid > 0) { Database::pexecute($ins_stmt, [ 'rowid' => $row['id'], 'ipportid' => $ssl_ipportid ], true, true); } } } if ($result['aliasdomain'] != $aliasdomain && is_numeric($result['aliasdomain'])) { // trigger when domain id for alias destination has changed: both for old and new destination Domain::triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger()); Domain::triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger()); } if ($result['wwwserveralias'] != $wwwserveralias || $result['letsencrypt'] != $letsencrypt) { // or when wwwserveralias or letsencrypt was changed if ((int)$aliasdomain === 0) { // in case the wwwserveralias is set on a main domain, $aliasdomain is 0 Domain::triggerLetsEncryptCSRForAliasDestinationDomain($id, $this->logger()); } else { Domain::triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger()); } } $idna_convert = new IdnaWrapper(); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] updated domain '" . $idna_convert->decode($result['domain']) . "'"); $result = $this->apiCall('Domains.get', [ 'domainname' => $result['domain'] ]); return $this->response($result); } throw new Exception("Not allowed to execute given command.", 403); } /** * delete a domain entry by either id or domainname * * @param int $id * optional, the domain-id * @param string $domainname * optional, the domainname * @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 * * @access admin * @return string json-encoded array * @throws Exception */ public function delete() { if ($this->isAdmin()) { $id = $this->getParam('id', true, 0); $dn_optional = $id > 0; $domainname = $this->getParam('domainname', $dn_optional, ''); $is_stdsubdomain = $this->getParam('is_stdsubdomain', true, 0); $result = $this->apiCall('Domains.get', [ 'id' => $id, 'domainname' => $domainname ]); $id = $result['id']; $subresult_stmt = Database::prepare(" SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE (`id` = :id OR `parentdomainid` = :id) "); Database::pexecute($subresult_stmt, [ 'id' => $id ], true, true); $idString = []; $paramString = []; while ($subRow = $subresult_stmt->fetch(PDO::FETCH_ASSOC)) { $idString[] = "`domainid` = :domain_" . (int)$subRow['id']; $paramString['domain_' . $subRow['id']] = $subRow['id']; } $idString = implode(' OR ', $idString); if ($idString != '') { $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_MAIL_USERS . "` WHERE " . $idString); Database::pexecute($del_stmt, $paramString, true, true); $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE " . $idString); Database::pexecute($del_stmt, $paramString, true, true); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "[API] deleted domain/s from mail-tables"); } $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `id` = :id OR `parentdomainid` = :id "); Database::pexecute($del_stmt, [ 'id' => $id ], true, true); $deleted_domains = $del_stmt->rowCount(); if ($is_stdsubdomain == 0) { $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `subdomains_used` = `subdomains_used` - :domaincount WHERE `customerid` = :customerid"); Database::pexecute($upd_stmt, [ 'domaincount' => ($deleted_domains - 1), 'customerid' => $result['customerid'] ], true, true); $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` - 1 WHERE `adminid` = :adminid"); Database::pexecute($upd_stmt, [ 'adminid' => $this->getUserDetail('adminid') ], true, true); } $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `standardsubdomain` = '0' WHERE `standardsubdomain` = :id AND `customerid` = :customerid"); Database::pexecute($upd_stmt, [ 'id' => $result['id'], 'customerid' => $result['customerid'] ], true, true); $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :domainid"); Database::pexecute($del_stmt, [ 'domainid' => $id ], true, true); $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_PANEL_DOMAINREDIRECTS . "` WHERE `did` = :domainid"); Database::pexecute($del_stmt, [ 'domainid' => $id ], true, true); // remove certificate from domain_ssl_settings, fixes #1596 $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` WHERE `domainid` = :domainid"); Database::pexecute($del_stmt, [ 'domainid' => $id ], true, true); // remove possible existing DNS entries $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_DOMAIN_DNS . "` WHERE `domain_id` = :domainid "); Database::pexecute($del_stmt, [ 'domainid' => $id ], true, true); if ((int)$result['aliasdomain'] !== 0) { Domain::triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger()); } // remove domains DNS from powerDNS if used, #581 Cronjob::inserttask(TaskId::DELETE_DOMAIN_PDNS, $result['domain']); // remove domain from acme.sh / lets encrypt if used Cronjob::inserttask(TaskId::DELETE_DOMAIN_SSL, $result['domain']); $this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_WARNING, "[API] deleted domain/subdomains (#" . $result['id'] . ")"); User::updateCounters(); Cronjob::inserttask(TaskId::REBUILD_VHOST); // Using nameserver, insert a task which rebuilds the server config Cronjob::inserttask(TaskId::REBUILD_DNS); return $this->response($result); } 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']); $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); } }