diff --git a/lib/classes/api/commands/class.Domains.php b/lib/classes/api/commands/class.Domains.php index a925d9b0..0720a57a 100644 --- a/lib/classes/api/commands/class.Domains.php +++ b/lib/classes/api/commands/class.Domains.php @@ -75,13 +75,13 @@ class Domains extends ApiCommand implements ResourceEntity $dn_optional = ($id <= 0 ? false : true); $domainname = $this->getParam('domainname', $dn_optional, ''); $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 idna_convert_wrapper(); $domainname = $idna_convert->encode($domainname); } - + $result_stmt = Database::prepare(" SELECT `d`.*, `c`.`customerid` FROM `" . TABLE_PANEL_DOMAINS . "` `d` @@ -108,6 +108,72 @@ class Domains extends ApiCommand implements ResourceEntity /** * add new domain entry * + * @param string $domain + * domain-name + * @param int $customerid + * @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 bool $subcanemaildomain + * optional, allow subdomains of this domain as email domains, default 0 (false) + * @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 int $alias + * optional, domain-id of a domain that the new domain should be an alias of, default 0 (none) + * @param bool $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 + * 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 bool $notryfiles + * optional, [nginx only] do not generate the default try-files directive, default 0 (false) + * @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 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 $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 + * @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 or not to add subdomains to the HSTS header + * @param bool $hsts_preload + * optional whether or not to preload HSTS header value + * @param bool $ocsp_stapling + * optional whether to enable oscp-stapling for this domain. default ß (false), requires SSL + * * @access admin * @throws Exception * @return array @@ -116,11 +182,11 @@ class Domains extends ApiCommand implements ResourceEntity { if ($this->isAdmin()) { if ($this->getUserDetail('domains_used') < $this->getUserDetail('domains') || $this->getUserDetail('domains') == '-1') { - + // parameters $p_domain = $this->getParam('domain'); $customerid = intval($this->getParam('customerid')); - + // optional parameters $p_ipandports = $this->getParam('ipandport', true, explode(',', Settings::Get('system.defaultip'))); $adminid = intval($this->getParam('adminid', true, $this->getUserDetail('adminid'))); @@ -153,22 +219,22 @@ class Domains extends ApiCommand implements ResourceEntity $hsts_sub = $this->getParam('hsts_sub', true, 0); $hsts_preload = $this->getParam('hsts_preload', true, 0); $ocsp_stapling = $this->getParam('ocsp_stapling', true, 0); - + // validation if ($p_domain == Settings::Get('system.hostname')) { standard_error('admin_domain_emailsystemhostname', '', true); } - + if (substr($p_domain, 0, 4) == 'xn--') { standard_error('domain_nopunycode', '', true); } - + $idna_convert = new idna_convert_wrapper(); $domain = $idna_convert->encode(preg_replace(array( '/\:(\d)+$/', '/^https?\:\/\//' ), '', validate($p_domain, 'domain'))); - + // Check whether domain validation is enabled and if, validate the domain if (Settings::Get('system.validate_domain') && ! validateDomain($domain)) { standard_error(array( @@ -176,23 +242,24 @@ class Domains extends ApiCommand implements ResourceEntity 'mydomain' ), '', true); } - + $customer = $this->apiCall('Customers.get', array( 'id' => $customerid )); - - if ($this->getUserDetail('customers_see_all') == '1') { + + 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, array( 'adminid' => $adminid ), true, true); - } else { - $adminid = $this->getUserDetail('adminid'); - $admin = $this->getUserData(); + if (empty($admin)) { + dynamic_error("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 = ''; @@ -200,7 +267,7 @@ class Domains extends ApiCommand implements ResourceEntity $path_suffix = '/' . $domain; } $_documentroot = makeCorrectDir($customer['documentroot'] . $path_suffix); - + $registration_date = validate($registration_date, 'registration_date', '/^(19|20)\d\d[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$/', '', array( '0000-00-00', '0', @@ -209,7 +276,7 @@ class Domains extends ApiCommand implements ResourceEntity if ($registration_date == '0000-00-00') { $registration_date = null; } - + $termination_date = validate($termination_date, 'termination_date', '/^(19|20)\d\d[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$/', '', array( '0000-00-00', '0', @@ -218,7 +285,7 @@ class Domains extends ApiCommand implements ResourceEntity if ($termination_date == '0000-00-00') { $termination_date = null; } - + if ($this->getUserDetail('change_serversettings') == '1') { if (Settings::Get('system.bind_enable') == '1') { $zonefile = validate($zonefile, 'zonefile', '', '', array(), true); @@ -226,13 +293,13 @@ class Domains extends ApiCommand implements ResourceEntity $isbinddomain = 0; $zonefile = ''; } - + $specialsettings = validate(str_replace("\r\n", "\n", $specialsettings), 'specialsettings', '/^[^\0]*$/', '', array(), true); validate($documentroot, 'documentroot', '', '', array(), 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 ($documentroot != '') { + if (! empty($documentroot)) { if (substr($documentroot, 0, 1) != '/' && ! preg_match('/^https?\:\/\//', $documentroot)) { $documentroot = $_documentroot . '/' . $documentroot; } @@ -251,9 +318,9 @@ class Domains extends ApiCommand implements ResourceEntity $notryfiles = '0'; $documentroot = $_documentroot; } - + 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 . "` @@ -261,11 +328,11 @@ class Domains extends ApiCommand implements ResourceEntity $phpsettingid_check = Database::pexecute_first($phpsettingid_check_stmt, array( 'phpsettingid' => $phpsettingid ), true, true); - + if (! isset($phpsettingid_check['id']) || $phpsettingid_check['id'] == '0' || $phpsettingid_check['id'] != $phpsettingid) { standard_error('phpsettingidwrong', '', true); } - + if ((int) Settings::Get('system.mod_fcgid') == 1) { $mod_fcgid_starter = validate($mod_fcgid_starter, 'mod_fcgid_starter', '/^[0-9]*$/', '', array( '-1', @@ -280,7 +347,7 @@ class Domains extends ApiCommand implements ResourceEntity $mod_fcgid_maxrequests = '-1'; } } else { - + if ((int) Settings::Get('phpfpm.enabled') == 1) { $phpsettingid = Settings::Get('phpfpm.defaultini'); } else { @@ -290,10 +357,10 @@ class Domains extends ApiCommand implements ResourceEntity $mod_fcgid_maxrequests = '-1'; } } else { - + $phpenabled = '1'; $openbasedir = '1'; - + if ((int) Settings::Get('phpfpm.enabled') == 1) { $phpsettingid = Settings::Get('phpfpm.defaultini'); } else { @@ -302,7 +369,7 @@ class Domains extends ApiCommand implements ResourceEntity $mod_fcgid_starter = '-1'; $mod_fcgid_maxrequests = '-1'; } - + // check non-ssl IP $ipandports = $this->validateIpAddresses($p_ipandports); // check ssl IP @@ -317,16 +384,16 @@ class Domains extends ApiCommand implements ResourceEntity // 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; } - + // We can't enable let's encrypt for wildcard - domains if using acme-v1 if ($serveraliasoption == '0' && $letsencrypt == '1' && Settings::Get('system.leapiversion') == '1') { standard_error('nowildcardwithletsencrypt', '', true); @@ -336,12 +403,12 @@ class Domains extends ApiCommand implements ResourceEntity if ($serveraliasoption == '0' && $letsencrypt == '1' && Settings::Get('system.leapiversion') == '2') { standard_error('nowildcardwithletsencryptv2', '', 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) { standard_error('pathmaynotcontaincolon', '', true); @@ -349,7 +416,7 @@ class Domains extends ApiCommand implements ResourceEntity $documentroot = makeCorrectDir($documentroot); } } - + $domain_check_stmt = Database::prepare(" SELECT `id`, `domain` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `domain` = :domain"); @@ -359,7 +426,7 @@ class Domains extends ApiCommand implements ResourceEntity $aliasdomain_check = array( 'id' => 0 ); - + if ($aliasdomain != 0) { // Overwrite given ipandports with these of the "main" domain $ipandports = array(); @@ -381,13 +448,13 @@ class Domains extends ApiCommand implements ResourceEntity $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 @@ -400,57 +467,57 @@ class Domains extends ApiCommand implements ResourceEntity ); $aliasdomain_check = Database::pexecute_first($aliasdomain_check_stmt, $alias_params, true, true); } - + if (count($ipandports) == 0) { standard_error('noipportgiven', '', true); } - + if ($phpenabled != '1') { $phpenabled = '0'; } - + if ($openbasedir != '1') { $openbasedir = '0'; } - + if ($speciallogfile != '1') { $speciallogfile = '0'; } - + if ($isbinddomain != '1') { $isbinddomain = '0'; } - + if ($isemaildomain != '1') { $isemaildomain = '0'; } - + if ($email_only == '1') { $isemaildomain = '1'; } else { $email_only = '0'; } - + if ($subcanemaildomain != '1' && $subcanemaildomain != '2' && $subcanemaildomain != '3') { $subcanemaildomain = '0'; } - + if ($dkim != '1') { $dkim = '0'; } - + if ($serveraliasoption != '1' && $serveraliasoption != '2') { $serveraliasoption = '0'; } - + if ($caneditdomain != '1') { $caneditdomain = '0'; } - + if ($issubof <= '0') { $issubof = '0'; } - + $idna_convert = new idna_convert_wrapper(); if ($domain == '') { standard_error(array( @@ -471,7 +538,7 @@ class Domains extends ApiCommand implements ResourceEntity } else { $wwwserveralias = ($serveraliasoption == '1') ? '1' : '0'; $iswildcarddomain = ($serveraliasoption == '0') ? '1' : '0'; - + $ins_data = array( 'domain' => $domain, 'customerid' => $customerid, @@ -507,7 +574,7 @@ class Domains extends ApiCommand implements ResourceEntity 'hsts_preload' => $hsts_preload, 'ocsp_stapling' => $ocsp_stapling ); - + $ins_stmt = Database::prepare(" INSERT INTO `" . TABLE_PANEL_DOMAINS . "` SET `domain` = :domain, @@ -551,20 +618,20 @@ class Domains extends ApiCommand implements ResourceEntity $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, array( '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 = array( 'domainid' => $domainid, @@ -572,7 +639,7 @@ class Domains extends ApiCommand implements ResourceEntity ); Database::pexecute($ins_stmt, $ins_data, true, true); } - + foreach ($ssl_ipandports as $ssl_ipportid) { if ($ssl_ipportid > 0) { $ins_data = array( @@ -582,15 +649,15 @@ class Domains extends ApiCommand implements ResourceEntity Database::pexecute($ins_stmt, $ins_data, true, true); } } - + triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger()); - + inserttask('1'); // Using nameserver, insert a task which rebuilds the server config inserttask('4'); - + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] added domain '" . $domain . "'"); - + $result = $this->apiCall('Domains.get', array( 'domainname' => $domain )); @@ -609,6 +676,76 @@ class Domains extends ApiCommand implements ResourceEntity * optional, the domain-id * @param string $domainname * optional, the domainname + * @param int $customerid + * optional customer-id + * @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 bool $subcanemaildomain + * optional, allow subdomains of this domain as email domains, default 0 (false) + * @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 bool $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 + * 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 bool $specialsettingsforsubdomains + * optional, whether to apply specialsettings to all subdomains of this domain, default 0 (false) + * @param bool $notryfiles + * optional, [nginx only] do not generate the default try-files directive, default 0 (false) + * @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 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 0 (false) + * @param bool $openbasedir + * optional, whether to activate openbasedir restriction for this domain, default 0 (false) + * @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 + * @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 or not to add subdomains to the HSTS header + * @param bool $hsts_preload + * optional whether or not to preload HSTS header value + * @param bool $ocsp_stapling + * optional whether to enable oscp-stapling for this domain. default ß (false), requires SSL * * @access admin * @throws Exception @@ -616,25 +753,25 @@ class Domains extends ApiCommand implements ResourceEntity */ public function update() { - if ($this->isAdmin() && $this->getUserDetail('change_serversettings')) { - + if ($this->isAdmin()) { + // parameters $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); $domainname = $this->getParam('domainname', $dn_optional, ''); - + // get requested domain $result = $this->apiCall('Domains.get', array( 'id' => $id, 'domainname' => $domainname )); $id = $result['id']; - + // optional parameters $p_ipandports = $this->getParam('ipandport', true, array()); $customerid = intval($this->getParam('customerid', true, $result['customerid'])); $adminid = intval($this->getParam('adminid', true, $result['adminid'])); - + $subcanemaildomain = $this->getParam('subcanemaildomain', true, $result['subcanemaildomain']); $isemaildomain = $this->getParam('isemaildomain', true, $result['isemaildomain']); $email_only = $this->getParam('email_only', true, $result['email_only']); @@ -667,7 +804,7 @@ class Domains extends ApiCommand implements ResourceEntity $hsts_sub = $this->getParam('hsts_sub', true, $result['hsts_sub']); $hsts_preload = $this->getParam('hsts_preload', true, $result['hsts_preload']); $ocsp_stapling = $this->getParam('ocsp_stapling', true, $result['ocsp_stapling']); - + // count subdomain usage of source-domain $subdomains_stmt = Database::prepare(" SELECT COUNT(`id`) AS count FROM `" . TABLE_PANEL_DOMAINS . "` WHERE @@ -677,7 +814,7 @@ class Domains extends ApiCommand implements ResourceEntity '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 @@ -687,7 +824,7 @@ class Domains extends ApiCommand implements ResourceEntity '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` @@ -697,11 +834,11 @@ class Domains extends ApiCommand implements ResourceEntity '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(' ', makeCorrectDestination($domain_emails_row['destination'])); @@ -712,7 +849,7 @@ class Domains extends ApiCommand implements ResourceEntity } } } - + // 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 @@ -739,18 +876,18 @@ class Domains extends ApiCommand implements ResourceEntity } } else { $customerid = $result['customerid']; - + // get customer $customer = $this->apiCall('Customers.get', array( 'id' => $customerid )); } - + // 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' ) @@ -758,7 +895,7 @@ class Domains extends ApiCommand implements ResourceEntity $admin = Database::pexecute_first($admin_stmt, array( 'adminid' => $adminid ), true, true); - + if (empty($admin) || $admin['adminid'] != $adminid) { standard_error('admindoesntexist', '', true); } @@ -768,7 +905,7 @@ class Domains extends ApiCommand implements ResourceEntity } else { $adminid = $result['adminid']; } - + $registration_date = validate($registration_date, 'registration_date', '/^(19|20)\d\d[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$/', '', array( '0000-00-00', '0', @@ -785,7 +922,7 @@ class Domains extends ApiCommand implements ResourceEntity if ($termination_date == '0000-00-00') { $termination_date = null; } - + $serveraliasoption = '2'; if ($result['iswildcarddomain'] == '1') { $serveraliasoption = '0'; @@ -795,23 +932,23 @@ class Domains extends ApiCommand implements ResourceEntity if ($p_serveraliasoption > - 1) { $serveraliasoption = $p_serveraliasoption; } - + if ($this->getUserDetail('change_serversettings') == '1') { - + if (Settings::Get('system.bind_enable') != '1') { $zonefile = validate($zonefile, 'zonefile', '', '', array(), true); } else { $isbinddomain = $result['isbinddomain']; $zonefile = $result['zonefile']; } - + if (Settings::Get('dkim.use_dkim') != '1') { $dkim = $result['dkim']; } - + $specialsettings = validate(str_replace("\r\n", "\n", $specialsettings), 'specialsettings', '/^[^\0]*$/', '', array(), true); $documentroot = validate($documentroot, 'documentroot', '', '', array(), 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') { @@ -823,7 +960,7 @@ class Domains extends ApiCommand implements ResourceEntity // 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 @@ -833,7 +970,7 @@ class Domains extends ApiCommand implements ResourceEntity $documentroot = $customer['documentroot']; } } - + if (! preg_match('/^https?\:\/\//', $documentroot) && strstr($documentroot, ":") !== false) { standard_error('pathmaynotcontaincolon', '', true); } @@ -846,9 +983,9 @@ class Domains extends ApiCommand implements ResourceEntity $notryfiles = $result['notryfiles']; $documentroot = $result['documentroot']; } - + 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 @@ -856,11 +993,11 @@ class Domains extends ApiCommand implements ResourceEntity $phpsettingid_check = Database::pexecute_first($phpsettingid_check_stmt, array( 'phpid' => $phpsettingid ), true, true); - + if (! isset($phpsettingid_check['id']) || $phpsettingid_check['id'] == '0' || $phpsettingid_check['id'] != $phpsettingid) { standard_error('phpsettingidwrong', '', true); } - + if ((int) Settings::Get('system.mod_fcgid') == 1) { $mod_fcgid_starter = validate($mod_fcgid_starter, 'mod_fcgid_starter', '/^[0-9]*$/', '', array( '-1', @@ -888,7 +1025,7 @@ class Domains extends ApiCommand implements ResourceEntity $mod_fcgid_starter = $result['mod_fcgid_starter']; $mod_fcgid_maxrequests = $result['mod_fcgid_maxrequests']; } - + // check non-ssl IP $ipandports = $this->validateIpAddresses($p_ipandports, false, $result['id']); // check ssl IP @@ -903,12 +1040,12 @@ class Domains extends ApiCommand implements ResourceEntity // 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; } @@ -922,54 +1059,54 @@ class Domains extends ApiCommand implements ResourceEntity if ($serveraliasoption == '0' && $letsencrypt == '1' && Settings::Get('system.leapiversion') == '2') { standard_error('nowildcardwithletsencryptv2', '', true); } - + // Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated if ($ssl_redirect > 0 && $letsencrypt == 1 && $result['letsencrypt'] != $letsencrypt) { $ssl_redirect = 2; } - + if (! preg_match('/^https?\:\/\//', $documentroot)) { $documentroot = makeCorrectDir($documentroot); } - + if ($phpenabled != '1') { $phpenabled = '0'; } - + if ($openbasedir != '1') { $openbasedir = '0'; } - + if ($isbinddomain != '1') { $isbinddomain = '0'; } - + if ($isemaildomain != '1') { $isemaildomain = '0'; } - + if ($email_only == '1') { $isemaildomain = '1'; } else { $email_only = '0'; } - + if ($subcanemaildomain != '1' && $subcanemaildomain != '2' && $subcanemaildomain != '3') { $subcanemaildomain = '0'; } - + if ($dkim != '1') { $dkim = '0'; } - + if ($caneditdomain != '1') { $caneditdomain = '0'; } - + $aliasdomain_check = array( 'id' => 0 ); - + if ($aliasdomain != 0) { // Overwrite given ipandports with these of the "main" domain $ipandports = array(); @@ -991,13 +1128,13 @@ class Domains extends ApiCommand implements ResourceEntity $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 @@ -1010,34 +1147,34 @@ class Domains extends ApiCommand implements ResourceEntity 'aliasdomain' => $aliasdomain ), true, true); } - + if (count($ipandports) == 0) { standard_error('noipportgiven', '', true); } - + if ($aliasdomain_check['id'] != $aliasdomain) { standard_error('domainisaliasorothercustomer', '', true); } - + if ($issubof <= '0') { $issubof = '0'; } - + 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'] || $notryfiles != $result['notryfiles'] || $aliasdomain != $result['aliasdomain'] || $issubof != $result['ismainbutsubto'] || $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']) { inserttask('1'); } - + if ($speciallogfile != $result['speciallogfile'] && $speciallogverified != '1') { $speciallogfile = $result['speciallogfile']; } - + if ($isbinddomain != $result['isbinddomain'] || $zonefile != $result['zonefile'] || $dkim != $result['dkim'] || $isemaildomain != $result['isemaildomain']) { inserttask('4'); } @@ -1045,7 +1182,7 @@ class Domains extends ApiCommand implements ResourceEntity if ($isbinddomain != $result['isbinddomain'] && $isbinddomain == 0) { inserttask('11', $result['domain']); } - + if ($isemaildomain == '0' && $result['isemaildomain'] == '1') { $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_MAIL_USERS . "` WHERE `domainid` = :id @@ -1053,7 +1190,7 @@ class Domains extends ApiCommand implements ResourceEntity Database::pexecute($del_stmt, array( 'id' => $id ), true, true); - + $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_MAIL_VIRTUAL . "` WHERE `domainid` = :id "); @@ -1062,7 +1199,7 @@ class Domains extends ApiCommand implements ResourceEntity ), true, true); $this->logger()->logAction(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(" @@ -1072,15 +1209,15 @@ class Domains extends ApiCommand implements ResourceEntity 'id' => $id ), true, true); } - + $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 = array( 'customerid' => $customerid, @@ -1110,7 +1247,7 @@ class Domains extends ApiCommand implements ResourceEntity 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 @@ -1122,7 +1259,7 @@ class Domains extends ApiCommand implements ResourceEntity "); 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 @@ -1130,7 +1267,7 @@ class Domains extends ApiCommand implements ResourceEntity Database::pexecute($upd_stmt, array( 'adminid' => $adminid ), true, true); - + $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` - 1 WHERE `adminid` = :adminid "); @@ -1138,9 +1275,9 @@ class Domains extends ApiCommand implements ResourceEntity 'adminid' => $result['adminid'] ), true, true); } - + $_update_data = array(); - + if ($ssfs == 1) { $_update_data['specialsettings'] = $specialsettings; $upd_specialsettings = ", `specialsettings` = :specialsettings "; @@ -1155,10 +1292,10 @@ class Domains extends ApiCommand implements ResourceEntity ), true, true); $this->logger()->logAction(ADM_ACTION, LOG_INFO, "[API] removed specialsettings on all subdomains of domain #" . $id); } - + $wwwserveralias = ($serveraliasoption == '1') ? '1' : '0'; $iswildcarddomain = ($serveraliasoption == '0') ? '1' : '0'; - + $update_data = array(); $update_data['customerid'] = $customerid; $update_data['adminid'] = $adminid; @@ -1192,7 +1329,7 @@ class Domains extends ApiCommand implements ResourceEntity $update_data['hsts_preload'] = $hsts_preload; $update_data['ocsp_stapling'] = $ocsp_stapling; $update_data['id'] = $id; - + $update_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_DOMAINS . "` SET `customerid` = :customerid, @@ -1229,7 +1366,7 @@ class Domains extends ApiCommand implements ResourceEntity WHERE `id` = :id "); Database::pexecute($update_stmt, $update_data, true, true); - + $_update_data['customerid'] = $customerid; $_update_data['adminid'] = $adminid; $_update_data['phpenabled'] = $phpenabled; @@ -1237,14 +1374,14 @@ class Domains extends ApiCommand implements ResourceEntity $_update_data['mod_fcgid_starter'] = $mod_fcgid_starter; $_update_data['mod_fcgid_maxrequests'] = $mod_fcgid_maxrequests; $_update_data['parentdomainid'] = $id; - + // 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 @@ -1252,7 +1389,7 @@ class Domains extends ApiCommand implements ResourceEntity 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, @@ -1265,13 +1402,10 @@ class Domains extends ApiCommand implements ResourceEntity WHERE `parentdomainid` = :parentdomainid "); Database::pexecute($_update_stmt, $_update_data, true, true); - - // FIXME check how many we got and if the amount of assigned IP's - // has changed so we can insert a config-rebuild task if only - // the ip's of this domain were changed - // -> for now, always insert a rebuild-task + + // insert a rebuild-task inserttask('1'); - + // Cleanup domain <-> ip mapping $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :id @@ -1279,11 +1413,11 @@ class Domains extends ApiCommand implements ResourceEntity Database::pexecute($del_stmt, array( '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, array( 'domainid' => $id, @@ -1298,7 +1432,7 @@ class Domains extends ApiCommand implements ResourceEntity ), true, true); } } - + // Cleanup domain <-> ip mapping for subdomains $domainidsresult_stmt = Database::prepare(" SELECT `id` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `parentdomainid` = :id @@ -1306,22 +1440,22 @@ class Domains extends ApiCommand implements ResourceEntity Database::pexecute($domainidsresult_stmt, array( '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, array( '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, array( 'rowid' => $row['id'], @@ -1347,11 +1481,11 @@ class Domains extends ApiCommand implements ResourceEntity if ($aliasdomain === 0) { // in case the wwwserveralias is set on a main domain, $aliasdomain is 0 // --> the call just above to triggerLetsEncryptCSRForAliasDestinationDomain - // is a noop...let's repeat it with the domain id of the main domain + // is a noop...let's repeat it with the domain id of the main domain triggerLetsEncryptCSRForAliasDestinationDomain($id, $this->logger()); } } - + $this->logger()->logAction(ADM_ACTION, LOG_WARNING, "[API] updated domain '" . $result['domain'] . "'"); return $this->response(200, "successfull", $update_data); } @@ -1382,19 +1516,19 @@ class Domains extends ApiCommand implements ResourceEntity $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', array( 'id' => $id, 'domainname' => $domainname )); $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 . ")"); @@ -1408,7 +1542,7 @@ class Domains extends ApiCommand implements ResourceEntity $paramString['domain_' . $subRow['id']] = $subRow['id']; } $idString = implode(' OR ', $idString); - + if ($idString != '') { $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_MAIL_USERS . "` WHERE " . $idString); @@ -1418,7 +1552,7 @@ class Domains extends ApiCommand implements ResourceEntity Database::pexecute($del_stmt, $paramString, true, true); $this->logger()->logAction(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) { @@ -1432,16 +1566,16 @@ class Domains extends ApiCommand implements ResourceEntity 'deletedMainDomainId' => $id ), true, true); } - + $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `id` = :id OR `parentdomainid` = :id " . $rsd_sql); Database::pexecute($del_stmt, array( 'id' => $id ), true, true); - + $deleted_domains = $del_stmt->rowCount(); - + if ($is_stdsubdomain == 0) { $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET @@ -1451,7 +1585,7 @@ class Domains extends ApiCommand implements ResourceEntity 'domaincount' => ($deleted_domains - 1), 'customerid' => $result['customerid'] ), true, true); - + $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_ADMINS . "` SET `domains_used` = `domains_used` - 1 @@ -1460,7 +1594,7 @@ class Domains extends ApiCommand implements ResourceEntity 'adminid' => $this->getUserDetail('adminid') ), true, true); } - + $upd_stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `standardsubdomain` = '0' @@ -1469,21 +1603,21 @@ class Domains extends ApiCommand implements ResourceEntity 'id' => $result['id'], 'customerid' => $result['customerid'] ), true, true); - + $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_DOMAINTOIP . "` WHERE `id_domain` = :domainid"); Database::pexecute($del_stmt, array( 'domainid' => $id ), true, true); - + $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_PANEL_DOMAINREDIRECTS . "` WHERE `did` = :domainid"); Database::pexecute($del_stmt, array( 'domainid' => $id ), true, true); - + // remove certificate from domain_ssl_settings, fixes #1596 $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` @@ -1491,7 +1625,7 @@ class Domains extends ApiCommand implements ResourceEntity Database::pexecute($del_stmt, array( 'domainid' => $id ), true, true); - + // remove possible existing DNS entries $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_DOMAIN_DNS . "` @@ -1500,9 +1634,9 @@ class Domains extends ApiCommand implements ResourceEntity Database::pexecute($del_stmt, array( 'domainid' => $id ), true, true); - + triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger()); - + // remove domains DNS from powerDNS if used, #581 inserttask('11', $result['domain']); @@ -1536,7 +1670,7 @@ class Domains extends ApiCommand implements ResourceEntity 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 = array(); if (! empty($p_ipandports) && is_numeric($p_ipandports)) { @@ -1547,13 +1681,13 @@ class Domains extends ApiCommand implements ResourceEntity 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 = array(); if ($this->getUserDetail('ip') != "-1") { // handle multiple-ip-array - $additional_ip_condition = " AND `ip` IN (".implode(",", json_decode($this->getUserDetail('ip'), true)).") "; + $additional_ip_condition = " AND `ip` IN (" . implode(",", json_decode($this->getUserDetail('ip'), true)) . ") "; } if (! empty($p_ipandports) && is_array($p_ipandports)) { diff --git a/lib/classes/api/commands/class.SubDomains.php b/lib/classes/api/commands/class.SubDomains.php index 4f42bdf4..fc422f1f 100644 --- a/lib/classes/api/commands/class.SubDomains.php +++ b/lib/classes/api/commands/class.SubDomains.php @@ -41,6 +41,12 @@ class SubDomains extends ApiCommand implements ResourceEntity * 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 int $hsts_maxage + * optional max-age value for HSTS header, default 0 + * @param bool $hsts_sub + * optional whether or not to add subdomains to the HSTS header, default 0 + * @param bool $hsts_preload + * optional whether or not to preload HSTS header value, default 0 * @param int $customerid * required when called as admin, not needed when called as customer * @@ -54,7 +60,7 @@ class SubDomains extends ApiCommand implements ResourceEntity // parameters $subdomain = $this->getParam('subdomain'); $domain = $this->getParam('domain'); - + // optional parameters $aliasdomain = $this->getParam('alias', true, 0); $path = $this->getParam('path', true, ''); @@ -76,24 +82,24 @@ class SubDomains extends ApiCommand implements ResourceEntity $hsts_sub = 0; $hsts_preload = 0; } - + // get needed customer info to reduce the subdomain-usage-counter by one $customer = $this->getCustomerData('subdomains'); - + // validation if (substr($subdomain, 0, 4) == 'xn--') { standard_error('domain_nopunycode', '', true); } - + $idna_convert = new idna_convert_wrapper(); $subdomain = $idna_convert->encode(preg_replace(array( '/\:(\d)+$/', '/^https?\:\/\//' ), '', validate($subdomain, 'subdomain', '', 'subdomainiswrong', array(), true))); - + // merge the two parts together $completedomain = $subdomain . '.' . $domain; - + if (Settings::Get('system.validate_domain') && ! validateDomain($completedomain)) { standard_error(array( 'stringiswrong', @@ -103,7 +109,7 @@ class SubDomains extends ApiCommand implements ResourceEntity if ($completedomain == Settings::Get('system.hostname')) { standard_error('admin_domain_emailsystemhostname', '', true); } - + // check whether the domain already exists $completedomain_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_DOMAINS . "` @@ -116,12 +122,12 @@ class SubDomains extends ApiCommand implements ResourceEntity "domain" => $completedomain, "customerid" => $customer['customerid'] ), true, true); - + if ($completedomain_check) { // no exception so far - domain exists standard_error('domainexistalready', $completedomain, true); } - + // alias domain checked? if ($aliasdomain != 0) { // also check ip/port combination to be the same, #176 @@ -147,10 +153,11 @@ class SubDomains extends ApiCommand implements ResourceEntity } triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger()); } - + // validate / correct path/url of domain + $_doredirect = false; $path = $this->validateDomainDocumentRoot($path, $url, $customer, $completedomain, $_doredirect); - + if ($openbasedir_path != 1) { $openbasedir_path = 0; } @@ -168,7 +175,7 @@ class SubDomains extends ApiCommand implements ResourceEntity "domain" => $domain, "customerid" => $customer['customerid'] ), true, true); - + if (! $domain_check) { // the given main-domain standard_error('maindomainnonexist', $domain, true); @@ -197,7 +204,7 @@ class SubDomains extends ApiCommand implements ResourceEntity standard_error('sslredirectonlypossiblewithsslipport', '', true); } } - + if ($letsencrypt != 0) { // let's encrypt only works if there actually is a // ssl ip/port assigned to the domain @@ -207,12 +214,12 @@ class SubDomains extends ApiCommand implements ResourceEntity standard_error('letsencryptonlypossiblewithsslipport', '', true); } } - + // Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated if ($ssl_redirect > 0 && $letsencrypt == 1) { $ssl_redirect = 2; } - + // get the phpsettingid from parentdomain, #107 $phpsid_stmt = Database::prepare(" SELECT `phpsettingid` FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `id` = :id @@ -220,7 +227,7 @@ class SubDomains extends ApiCommand implements ResourceEntity $phpsid_result = Database::pexecute_first($phpsid_stmt, array( "id" => $domain_check['id'] ), true, true); - + if (! isset($phpsid_result['phpsettingid']) || (int) $phpsid_result['phpsettingid'] <= 0) { // assign default config $phpsid_result['phpsettingid'] = 1; @@ -229,7 +236,7 @@ class SubDomains extends ApiCommand implements ResourceEntity if ($phpsettingid > 0 && $phpsettingid != $phpsid_result['phpsettingid']) { $phpsid_result['phpsettingid'] = intval($phpsettingid); } - + // acutall insert domain $stmt = Database::prepare(" INSERT INTO `" . TABLE_PANEL_DOMAINS . "` SET @@ -278,7 +285,7 @@ class SubDomains extends ApiCommand implements ResourceEntity ); Database::pexecute($stmt, $params, true, true); $subdomain_id = Database::lastInsertId(); - + $stmt = Database::prepare(" INSERT INTO `" . TABLE_DOMAINTOIP . "` (`id_domain`, `id_ipandports`) @@ -289,20 +296,20 @@ class SubDomains extends ApiCommand implements ResourceEntity Database::pexecute($stmt, array( "id_domain" => $domain_check['id'] )); - + if ($_doredirect) { addRedirectToDomain($subdomain_id, $redirectcode); } - + inserttask('1'); // Using nameserver, insert a task which rebuilds the server config inserttask('4'); - + Customers::increaseUsage($customer['customerid'], 'subdomains_used'); Admins::increaseUsage(($this->isAdmin() ? $customer['adminid'] : $this->getUserDetail('adminid')), 'subdomains_used'); - + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] added subdomain '" . $completedomain . "'"); - + $result = $this->apiCall('SubDomains.get', array( 'id' => $subdomain_id )); @@ -328,13 +335,13 @@ class SubDomains extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); $domainname = $this->getParam('domainname', $dn_optional, ''); - + // convert possible idn domain to punycode if (substr($domainname, 0, 4) != 'xn--') { $idna_convert = new idna_convert_wrapper(); $domainname = $idna_convert->encode($domainname); } - + if ($this->isAdmin()) { if ($this->getUserDetail('customers_see_all') != 1) { // if it's a reseller or an admin who cannot see all customers, we need to check @@ -349,7 +356,7 @@ class SubDomains extends ApiCommand implements ResourceEntity $result_stmt = Database::prepare(" SELECT d.*, pd.`subcanemaildomain`, pd.`isbinddomain` as subisbinddomain FROM `" . TABLE_PANEL_DOMAINS . "` d, `" . TABLE_PANEL_DOMAINS . "` pd - WHERE " . ($id > 0 ? "d.`id` = :iddn" : "d.`domain` = :iddn") . " AND d.`customerid` IN (".implode(", ", $customer_ids).") + WHERE " . ($id > 0 ? "d.`id` = :iddn" : "d.`domain` = :iddn") . " AND d.`customerid` IN (" . implode(", ", $customer_ids) . ") AND ((d.`parentdomainid`!='0' AND pd.`id` = d.`parentdomainid`) OR (d.`parentdomainid`='0' AND pd.`id` = d.`id`)) "); $params = array( @@ -400,7 +407,35 @@ class SubDomains extends ApiCommand implements ResourceEntity * optional, the domain-id * @param string $domainname * optional, the domainname - * + * @param int $alias + * optional, domain-id of a domain that the new domain should be an alias of + * @param string $path + * optional, destination path relative to the customers-homedir, default is customers-homedir + * @param string $url + * optional, overwrites path value with an URL to generate a redirect, alternatively use the path parameter also for URLs + * @param int $selectserveralias + * optional, 0 = wildcard, 1 = www-alias, 2 = none + * @param bool $isemaildomain + * optional + * @param int $openbasedir_path + * optional, either 0 for customers-homedir or 1 for domains-docroot + * @param int $phpsettingid + * optional, php-settings-id, if empty the $domain value is used + * @param int $redirectcode + * optional, redirect-code-id from TABLE_PANEL_REDIRECTCODES + * @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 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 + * @param bool $hsts_preload + * optional whether or not to preload HSTS header value + * @param int $customerid + * required when called as admin, not needed when called as customer + * * @access admin, customer * @throws Exception * @return array @@ -410,7 +445,7 @@ class SubDomains extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); $domainname = $this->getParam('domainname', $dn_optional, ''); - + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'domains')) { throw new Exception("You cannot access this resource", 405); } @@ -420,7 +455,7 @@ class SubDomains extends ApiCommand implements ResourceEntity 'domainname' => $domainname )); $id = $result['id']; - + // parameters $aliasdomain = $this->getParam('alias', true, 0); $path = $this->getParam('path', true, $result['documentroot']); @@ -445,16 +480,16 @@ class SubDomains extends ApiCommand implements ResourceEntity $hsts_sub = 0; $hsts_preload = 0; } - + // get needed customer info to reduce the subdomain-usage-counter by one $customer = $this->getCustomerData(); - + $alias_stmt = Database::prepare("SELECT COUNT(`id`) AS count FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `aliasdomain`= :aliasdomain"); $alias_check = Database::pexecute_first($alias_stmt, array( "aliasdomain" => $result['id'] )); $alias_check = $alias_check['count']; - + // alias domain checked? if ($aliasdomain != 0) { $aliasdomain_stmt = Database::prepare(" @@ -474,26 +509,27 @@ class SubDomains extends ApiCommand implements ResourceEntity } triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger()); } - + // validate / correct path/url of domain + $_doredirect = false; $path = $this->validateDomainDocumentRoot($path, $url, $customer, $result['domain'], $_doredirect); - + // set alias-fields according to selected alias mode $iswildcarddomain = ($selectserveralias == '0') ? '1' : '0'; $wwwserveralias = ($selectserveralias == '1') ? '1' : '0'; - + // if allowed, check for 'is email domain'-flag if ($result['parentdomainid'] != '0' && ($result['subcanemaildomain'] == '1' || $result['subcanemaildomain'] == '2') && $isemaildomain != $result['isemaildomain']) { $isemaildomain = intval($isemaildomain); } else { $isemaildomain = $result['subcanemaildomain'] == '3' ? 1 : 0; } - + // check changes of openbasedir-path variable if ($openbasedir_path != 1) { $openbasedir_path = 0; } - + if ($ssl_redirect != 0) { // a ssl-redirect only works if there actually is a // ssl ip/port assigned to the domain @@ -504,7 +540,7 @@ class SubDomains extends ApiCommand implements ResourceEntity standard_error('sslredirectonlypossiblewithsslipport', '', true); } } - + if ($letsencrypt != 0) { // let's encrypt only works if there actually is a // ssl ip/port assigned to the domain @@ -514,7 +550,7 @@ class SubDomains extends ApiCommand implements ResourceEntity standard_error('letsencryptonlypossiblewithsslipport', '', true); } } - + // We can't enable let's encrypt for wildcard - domains when using acme-v1 if ($iswildcarddomain == '1' && $letsencrypt == '1' && Settings::Get('system.leapiversion') == '1') { standard_error('nowildcardwithletsencrypt'); @@ -524,12 +560,12 @@ class SubDomains extends ApiCommand implements ResourceEntity if ($iswildcarddomain == '0' && $letsencrypt == '1' && Settings::Get('system.leapiversion') == '2') { standard_error('nowildcardwithletsencryptv2'); } - + // Temporarily deactivate ssl_redirect until Let's Encrypt certificate was generated if ($ssl_redirect > 0 && $letsencrypt == 1 && $result['letsencrypt'] != $letsencrypt) { $ssl_redirect = 2; } - + // is-email-domain flag changed - remove mail accounts and mail-addresses if (($result['isemaildomain'] == '1') && $isemaildomain == '0') { $params = array( @@ -543,12 +579,12 @@ class SubDomains extends ApiCommand implements ResourceEntity $idna_convert = new idna_convert_wrapper(); $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_NOTICE, "[API] automatically deleted mail-table entries for '" . $idna_convert->decode($result['domain']) . "'"); } - + // handle redirect if ($_doredirect) { updateRedirectOfDomain($id, $redirectcode); } - + if ($path != $result['documentroot'] || $isemaildomain != $result['isemaildomain'] || $wwwserveralias != $result['wwwserveralias'] || $iswildcarddomain != $result['iswildcarddomain'] || $aliasdomain != $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']) { $stmt = Database::prepare(" UPDATE `" . TABLE_PANEL_DOMAINS . "` SET @@ -583,7 +619,7 @@ class SubDomains extends ApiCommand implements ResourceEntity "id" => $id ); Database::pexecute($stmt, $params, true, true); - + if ($result['aliasdomain'] != $aliasdomain) { // trigger when domain id for alias destination has changed: both for old and new destination triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger()); @@ -592,7 +628,7 @@ class SubDomains extends ApiCommand implements ResourceEntity // or when wwwserveralias or letsencrypt was changed triggerLetsEncryptCSRForAliasDestinationDomain($aliasdomain, $this->logger()); } - + // check whether LE has been disabled, so we remove the certificate if ($letsencrypt == '0' && $result['letsencrypt'] == '1') { $del_stmt = Database::prepare(" @@ -602,10 +638,10 @@ class SubDomains extends ApiCommand implements ResourceEntity 'id' => $id ), true, true); } - + inserttask('1'); inserttask('4'); - + $idna_convert = new idna_convert_wrapper(); $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_INFO, "[API] edited domain '" . $idna_convert->decode($result['domain']) . "'"); } @@ -629,7 +665,7 @@ class SubDomains extends ApiCommand implements ResourceEntity // or optionally for one specific customer identified by id or loginname $customerid = $this->getParam('customerid', true, 0); $loginname = $this->getParam('loginname', true, ''); - + if (! empty($customerid) || ! empty($loginname)) { $result = $this->apiCall('Customers.get', array( 'id' => $customerid, @@ -659,7 +695,7 @@ class SubDomains extends ApiCommand implements ResourceEntity $this->getUserDetail('customerid') => $this->getUserDetail('standardsubdomain') ); } - + // prepare select statement $domains_stmt = Database::prepare(" SELECT `d`.`id`, `d`.`customerid`, `d`.`domain`, `d`.`documentroot`, `d`.`isbinddomain`, `d`.`isemaildomain`, `d`.`caneditdomain`, `d`.`iswildcarddomain`, `d`.`parentdomainid`, `d`.`letsencrypt`, `d`.`termination_date`, `ad`.`id` AS `aliasdomainid`, `ad`.`domain` AS `aliasdomain`, `da`.`id` AS `domainaliasid`, `da`.`domain` AS `domainalias` @@ -670,7 +706,7 @@ class SubDomains extends ApiCommand implements ResourceEntity AND `d`.`email_only`='0' AND `d`.`id` <> :standardsubdomain "); - + $result = array(); foreach ($customer_ids as $customer_id) { Database::pexecute($domains_stmt, array( @@ -704,21 +740,21 @@ class SubDomains extends ApiCommand implements ResourceEntity $id = $this->getParam('id', true, 0); $dn_optional = ($id <= 0 ? false : true); $domainname = $this->getParam('domainname', $dn_optional, ''); - + if ($this->isAdmin() == false && Settings::IsInList('panel.customer_hide_options', 'domains')) { throw new Exception("You cannot access this resource", 405); } - + $result = $this->apiCall('SubDomains.get', array( 'id' => $id, 'domainname' => $domainname )); $id = $result['id']; - + // get needed customer info to reduce the subdomain-usage-counter by one $customer = $this->getCustomerData(); - if (!$this->isAdmin() && $result['caneditdomain'] == 0) { + if (! $this->isAdmin() && $result['caneditdomain'] == 0) { throw new Exception("You cannot edit this resource", 405); } @@ -732,14 +768,14 @@ class SubDomains extends ApiCommand implements ResourceEntity "customerid" => $customer['customerid'], "domainid" => $id ), true, true); - + if ($emails['count'] != '0') { standard_error('domains_cantdeletedomainwithemail', '', true); } } - + triggerLetsEncryptCSRForAliasDestinationDomain($result['aliasdomain'], $this->logger()); - + // delete domain from table $stmt = Database::prepare(" DELETE FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `customerid` = :customerid AND `id` = :id @@ -748,7 +784,7 @@ class SubDomains extends ApiCommand implements ResourceEntity "customerid" => $customer['customerid'], "id" => $id ), true, true); - + // remove connections to ips and domainredirects $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_DOMAINTOIP . "` @@ -757,7 +793,7 @@ class SubDomains extends ApiCommand implements ResourceEntity Database::pexecute($del_stmt, array( 'domainid' => $id ), true, true); - + // remove redirect-codes $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_PANEL_DOMAINREDIRECTS . "` @@ -766,7 +802,7 @@ class SubDomains extends ApiCommand implements ResourceEntity Database::pexecute($del_stmt, array( 'domainid' => $id ), true, true); - + // remove certificate from domain_ssl_settings, fixes #1596 $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` @@ -775,7 +811,7 @@ class SubDomains extends ApiCommand implements ResourceEntity Database::pexecute($del_stmt, array( 'domainid' => $id ), true, true); - + // remove possible existing DNS entries $del_stmt = Database::prepare(" DELETE FROM `" . TABLE_DOMAIN_DNS . "` @@ -784,18 +820,18 @@ class SubDomains extends ApiCommand implements ResourceEntity Database::pexecute($del_stmt, array( 'domainid' => $id ), true, true); - + inserttask('1'); // Using nameserver, insert a task which rebuilds the server config inserttask('4'); // remove domains DNS from powerDNS if used, #581 inserttask('11', $result['domain']); - + // reduce subdomain-usage-counter Customers::decreaseUsage($customer['customerid'], 'subdomains_used'); // update admin usage Admins::decreaseUsage(($this->isAdmin() ? $customer['adminid'] : $this->getUserDetail('adminid')), 'subdomains_used'); - + $this->logger()->logAction($this->isAdmin() ? ADM_ACTION : USR_ACTION, LOG_WARNING, "[API] deleted subdomain '" . $result['domain'] . "'"); return $this->response(200, "successfull", $result); } @@ -821,7 +857,7 @@ class SubDomains extends ApiCommand implements ResourceEntity } else { $path = validate($path, 'path', '', '', array(), true); } - + // check whether path is a real path if (! preg_match('/^https?\:\/\//', $path) || ! validateUrl($path)) { if (strstr($path, ":") !== false) {