From 94c4d524764534856493651bccb4bb290a0a861a Mon Sep 17 00:00:00 2001 From: markuspetermann Date: Sat, 15 Sep 2018 12:41:27 +0200 Subject: [PATCH 1/9] Fixed class.lescript_v2.php to work with ACMEv2 As GET /document never returns any Reply-Nonce Header, getLastNonce() caused an infinite loop. The Content-Type for v2 must be application/jose+json. --- lib/classes/ssl/class.lescript_v2.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/classes/ssl/class.lescript_v2.php b/lib/classes/ssl/class.lescript_v2.php index 448222eb..4469546b 100644 --- a/lib/classes/ssl/class.lescript_v2.php +++ b/lib/classes/ssl/class.lescript_v2.php @@ -495,8 +495,8 @@ class Client private function curl($method, $url, $data = null) { $headers = array( - 'Accept: application/json', - 'Content-Type: application/json' + 'Accept: application/jose+json', + 'Content-Type: application/jose+json' ); $handle = curl_init(); curl_setopt($handle, CURLOPT_URL, preg_match('~^http~', $url) ? $url : $this->base . $url); @@ -550,7 +550,7 @@ class Client return trim($matches[1]); } - $this->curl('GET', '/directory'); + $this->curl('GET', '/acme/new-nonce'); return $this->getLastNonce(); } From 5f29b2cc4ad7c0a15535afc70015bb7f2fa2ddf7 Mon Sep 17 00:00:00 2001 From: Markus Petermann Date: Tue, 18 Sep 2018 00:53:51 +0200 Subject: [PATCH 2/9] Fixed class.lescript_v2.php to work with ACMEv2 When requesting a certificate for multiple Domains/SANs, the request must be done in a single order. --- lib/classes/ssl/class.lescript_v2.php | 69 ++++++++++++++++----------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/lib/classes/ssl/class.lescript_v2.php b/lib/classes/ssl/class.lescript_v2.php index 4469546b..2874ef34 100644 --- a/lib/classes/ssl/class.lescript_v2.php +++ b/lib/classes/ssl/class.lescript_v2.php @@ -149,42 +149,54 @@ class lescript_v2 // start domains authentication // ---------------------------- - + + // Prepare order + $domains_in_order = array(); foreach ($domains as $domain) { + $domains_in_order []= array( + "type" => "dns", + "value" => $domain + ); + } + + // Send new-order request + $response = $this->signedRequest($this->_req_uris['newOrder'], array( + "identifiers" => $domains_in_order + ), false); + + if ($this->client->getLastCode() == 403) { + $this->log("Got status 403 - setting LE status to unregistered."); + $this->setLeRegisteredState(0); + throw new RuntimeException("Got 'unauthorized' response - we need to re-register at next run. Whole response: " . json_encode($response)); + } + + // if response is not an array but a string, it's most likely a server-error, e.g. + // ErrorAn error occurred while processing your request. + //

Reference #179.d8be1402.1458059103.3613c4db + if (! is_array($response)) { + throw new RuntimeException("Invalid response from LE for domain $domain. Whole response: " . json_encode($response)); + } + + if (! array_key_exists('authorizations', $response)) { + throw new RuntimeException("No authorizations received for $domain. Whole response: " . json_encode($response)); + } + + $authorizations = $response['authorizations']; + $finalizeLink = $response['finalize']; + + $i = 0; + + foreach ($authorizations as $authorization) { // 1. getting available authentication options // ------------------------------------------- + + $domain = $response['identifiers'][$i++]['value']; $this->log("Requesting challenge for $domain"); - $response = $this->signedRequest($this->_req_uris['newOrder'], array( - "identifiers" => array( - array( - "type" => "dns", - "value" => $domain - ) - ) - ), false); - - if ($this->client->getLastCode() == 403) { - $this->log("Got status 403 - setting LE status to unregistered."); - $this->setLeRegisteredState(0); - throw new RuntimeException("Got 'unauthorized' response - we need to re-register at next run. Whole response: " . json_encode($response)); - } - - // if response is not an array but a string, it's most likely a server-error, e.g. - // ErrorAn error occurred while processing your request. - //

Reference #179.d8be1402.1458059103.3613c4db - if (! is_array($response)) { - throw new RuntimeException("Invalid response from LE for domain $domain. Whole response: " . json_encode($response)); - } - - if (! array_key_exists('authorizations', $response)) { - throw new RuntimeException("No authorizations received for $domain. Whole response: " . json_encode($response)); - } - // get authorization - $auth_response = $this->client->get($response['authorizations'][0]); + $auth_response = $this->client->get($authorization); if (! array_key_exists('challenges', $auth_response)) { throw new RuntimeException("No challenges received for $domain. Whole response: " . json_encode($auth_response)); @@ -201,7 +213,6 @@ class lescript_v2 $this->log("Got challenge token for $domain"); $location = $challenge['url']; - $finalizeLink = $response['finalize']; // 2. saving authentication token for web verification // --------------------------------------------------- From 4f0acd176a0f5e5ee61cd81b113e77e2bd22bb0f Mon Sep 17 00:00:00 2001 From: Markus Petermann Date: Tue, 18 Sep 2018 02:20:17 +0200 Subject: [PATCH 3/9] Fixed class.lescript_v2.php to work with ACMEv2 Account(kid) needs to be saved for future requests. Install/Update part is untested. --- install/froxlor.sql | 4 +++- install/updates/froxlor/0.9/update_0.9.inc.php | 13 +++++++++++++ lib/classes/ssl/class.lescript_v2.php | 6 +++++- scripts/jobs/cron_letsencrypt_v2.php | 2 ++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/install/froxlor.sql b/install/froxlor.sql index c00a730f..80091911 100644 --- a/install/froxlor.sql +++ b/install/froxlor.sql @@ -198,6 +198,7 @@ CREATE TABLE `panel_customers` ( `lepublickey` mediumtext default NULL, `leprivatekey` mediumtext default NULL, `leregistered` tinyint(1) NOT NULL default '0', + `leaccount` varchar(255) default '', `allowed_phpconfigs` varchar(500) NOT NULL default '', PRIMARY KEY (`customerid`), UNIQUE KEY `loginname` (`loginname`) @@ -653,6 +654,7 @@ opcache.interned_strings_buffer'), ('system', 'hsts_incsub', '0'), ('system', 'hsts_preload', '0'), ('system', 'leregistered', '0'), + ('system', 'leaccount', ''), ('system', 'nssextrausers', '0'), ('system', 'disable_le_selfcheck', '0'), ('system', 'ssl_protocols', 'TLSv1,TLSv1.2'), @@ -692,7 +694,7 @@ opcache.interned_strings_buffer'), ('panel', 'password_special_char', '!?<>§$%+#=@'), ('panel', 'customer_hide_options', ''), ('panel', 'version', '0.9.39.5'), - ('panel', 'db_version', '201805290'); + ('panel', 'db_version', '201809180'); DROP TABLE IF EXISTS `panel_tasks`; diff --git a/install/updates/froxlor/0.9/update_0.9.inc.php b/install/updates/froxlor/0.9/update_0.9.inc.php index 17432b94..5c3d8463 100644 --- a/install/updates/froxlor/0.9/update_0.9.inc.php +++ b/install/updates/froxlor/0.9/update_0.9.inc.php @@ -3998,3 +3998,16 @@ if (isDatabaseVersion('201805241')) { updateToDbVersion('201805290'); } } + +if (isDatabaseVersion('201805290')) { + + showUpdateStep("Adding leaccount field to panel customers"); + Database::query("ALTER TABLE `froxlor`.`panel_customers` ADD COLUMN `leaccount` varchar(255) default '' AFTER `leregistered`;"); + lastStepStatus(0); + + showUpdateStep("Adding system setting for let's-encrypt account"); + Settings::AddNew('system.leaccount', ""); + lastStepStatus(0); + + updateToDbVersion('201809180'); +} diff --git a/lib/classes/ssl/class.lescript_v2.php b/lib/classes/ssl/class.lescript_v2.php index 2874ef34..7c51f049 100644 --- a/lib/classes/ssl/class.lescript_v2.php +++ b/lib/classes/ssl/class.lescript_v2.php @@ -76,6 +76,7 @@ class lescript_v2 $this->customerId = (! $isFroxlorVhost ? $certrow['customerid'] : null); $this->isFroxlorVhost = $isFroxlorVhost; $this->isLeProduction = (Settings::Get('system.letsencryptca') == 'production'); + $this->_acc_location = $certrow['leaccount']; $leregistered = $certrow['leregistered']; @@ -166,6 +167,7 @@ class lescript_v2 if ($this->client->getLastCode() == 403) { $this->log("Got status 403 - setting LE status to unregistered."); + $this->_acc_location = ''; $this->setLeRegisteredState(0); throw new RuntimeException("Got 'unauthorized' response - we need to re-register at next run. Whole response: " . json_encode($response)); } @@ -347,10 +349,12 @@ class lescript_v2 if ($this->isLeProduction) { if ($this->isFroxlorVhost) { Settings::Set('system.leregistered', $state); + Settings::Set('system.leaccount', $this->_acc_location); } else { - $upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `leregistered` = :registered " . "WHERE `customerid` = :customerid;"); + $upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `leregistered` = :registered, `leaccount` = :kid " . "WHERE `customerid` = :customerid;"); Database::pexecute($upd_stmt, array( 'registered' => $state, + 'kid' => $this->_acc_location, 'customerid' => $this->customerId )); } diff --git a/scripts/jobs/cron_letsencrypt_v2.php b/scripts/jobs/cron_letsencrypt_v2.php index c56a3e4e..c476c11b 100644 --- a/scripts/jobs/cron_letsencrypt_v2.php +++ b/scripts/jobs/cron_letsencrypt_v2.php @@ -45,6 +45,7 @@ $certificates_stmt = Database::query(" cust.`leprivatekey`, cust.`lepublickey`, cust.`leregistered`, + cust.`leaccount`, cust.`customerid`, cust.`loginname` FROM @@ -109,6 +110,7 @@ if (Settings::Get('system.le_froxlor_enabled') == '1') { 'leprivatekey' => Settings::Get('system.leprivatekey'), 'lepublickey' => Settings::Get('system.lepublickey'), 'leregistered' => Settings::Get('system.leregistered'), + 'leaccount' => Settings::Get('system.leaccount'), 'ssl_redirect' => Settings::Get('system.le_froxlor_redirect'), 'expirationdate' => null, 'ssl_cert_file' => null, From f72d87228b9ff698472696785dbaa436a380599b Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Wed, 19 Sep 2018 11:07:54 +0200 Subject: [PATCH 4/9] restrict sending of emails when user is locked Signed-off-by: Michael Kaufmann --- lib/configfiles/gentoo.xml | 2 +- lib/configfiles/jessie.xml | 2 +- lib/configfiles/precise.xml | 2 +- lib/configfiles/rhel_centos.xml | 2 +- lib/configfiles/stretch.xml | 2 +- lib/configfiles/trusty.xml | 2 +- lib/configfiles/xenial.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/configfiles/gentoo.xml b/lib/configfiles/gentoo.xml index 7b68d374..5ca0d4ad 100644 --- a/lib/configfiles/gentoo.xml +++ b/lib/configfiles/gentoo.xml @@ -2138,7 +2138,7 @@ protocol lda { driver = mysql connect = host= dbname= user= password= default_pass_scheme = CRYPT -password_query = "SELECT username AS user, password_enc AS password, CONCAT(homedir, maildir) AS userdb_home, uid AS userdb_uid, gid AS userdb_gid, CONCAT('maildir:', homedir, maildir) AS userdb_mail, CONCAT('*:storage=', quota,'M') AS userdb_quota_rule FROM mail_users WHERE (username = '%u' OR email = '%u') AND ((imap = 1 AND '%Ls' = 'imap') OR (pop3 = 1 AND '%Ls' = 'pop3') OR '%Ls' = 'smtp' OR '%Ls' = 'sieve')" +password_query = "SELECT username AS user, password_enc AS password, CONCAT(homedir, maildir) AS userdb_home, uid AS userdb_uid, gid AS userdb_gid, CONCAT('maildir:', homedir, maildir) AS userdb_mail, CONCAT('*:storage=', quota,'M') AS userdb_quota_rule FROM mail_users WHERE (username = '%u' OR email = '%u') AND ((imap = 1 AND '%Ls' = 'imap') OR (pop3 = 1 AND '%Ls' = 'pop3') OR ((postfix = 'Y' AND '%Ls' = 'smtp') OR (postfix = 'Y' AND '%Ls' = 'sieve')))" user_query = "SELECT CONCAT(homedir, maildir) AS home, CONCAT('maildir:', homedir, maildir) AS mail, uid, gid, CONCAT('*:storage=', quota,'M') AS quota_rule FROM mail_users WHERE (username = '%u' OR email = '%u')" iterate_query = "SELECT username AS user FROM mail_users WHERE (imap = 1 OR pop3 = 1)" ]]> diff --git a/lib/configfiles/jessie.xml b/lib/configfiles/jessie.xml index fa60dcc2..2f3c5cde 100644 --- a/lib/configfiles/jessie.xml +++ b/lib/configfiles/jessie.xml @@ -2719,7 +2719,7 @@ user_query = SELECT CONCAT(homedir, maildir) AS home, CONCAT('maildir:', homedir # SELECT userid AS user, password, \ # home AS userdb_home, uid AS userdb_uid, gid AS userdb_gid \ # FROM users WHERE userid = '%u' -password_query = SELECT username AS user, password_enc AS password, CONCAT(homedir, maildir) AS userdb_home, uid AS userdb_uid, gid AS userdb_gid, CONCAT('maildir:', homedir, maildir) AS userdb_mail, CONCAT('*:storage=', quota, 'M') as userdb_quota_rule FROM mail_users WHERE (username = '%u' OR email = '%u') AND ((imap = 1 AND '%Ls' = 'imap') OR (pop3 = 1 AND '%Ls' = 'pop3') OR '%Ls' = 'smtp' OR '%Ls' = 'sieve') +password_query = SELECT username AS user, password_enc AS password, CONCAT(homedir, maildir) AS userdb_home, uid AS userdb_uid, gid AS userdb_gid, CONCAT('maildir:', homedir, maildir) AS userdb_mail, CONCAT('*:storage=', quota, 'M') as userdb_quota_rule FROM mail_users WHERE (username = '%u' OR email = '%u') AND ((imap = 1 AND '%Ls' = 'imap') OR (pop3 = 1 AND '%Ls' = 'pop3') OR ((postfix = 'Y' AND '%Ls' = 'smtp') OR (postfix = 'Y' AND '%Ls' = 'sieve'))) # Query to get a list of all usernames. #iterate_query = SELECT username AS user FROM users diff --git a/lib/configfiles/precise.xml b/lib/configfiles/precise.xml index 2e17ba72..9af58f3c 100644 --- a/lib/configfiles/precise.xml +++ b/lib/configfiles/precise.xml @@ -1034,7 +1034,7 @@ userdb { driver = mysql connect = host= dbname= user= password= default_pass_scheme = CRYPT -password_query = SELECT username AS user, password_enc AS password, CONCAT(homedir, maildir) AS userdb_home, uid AS userdb_uid, gid AS userdb_gid, CONCAT('maildir:', homedir, maildir) AS userdb_mail, CONCAT('maildir:storage=', (quota*1024)) as userdb_quota FROM mail_users WHERE (username = '%u' OR email = '%u') AND ((imap = 1 AND '%Ls' = 'imap') OR (pop3 = 1 AND '%Ls' = 'pop3') OR '%Ls' = 'smtp' OR '%Ls' = 'sieve') +password_query = SELECT username AS user, password_enc AS password, CONCAT(homedir, maildir) AS userdb_home, uid AS userdb_uid, gid AS userdb_gid, CONCAT('maildir:', homedir, maildir) AS userdb_mail, CONCAT('maildir:storage=', (quota*1024)) as userdb_quota FROM mail_users WHERE (username = '%u' OR email = '%u') AND ((imap = 1 AND '%Ls' = 'imap') OR (pop3 = 1 AND '%Ls' = 'pop3') OR ((postfix = 'Y' AND '%Ls' = 'smtp') OR (postfix = 'Y' AND '%Ls' = 'sieve'))) user_query = SELECT CONCAT(homedir, maildir) AS home, CONCAT('maildir:', homedir, maildir) AS mail, uid, gid, CONCAT('maildir:storage=', (quota*1024)) as quota FROM mail_users WHERE (username = '%u' OR email = '%u') iterate_query = SELECT username AS user FROM mail_users WHERE (imap = 1 OR pop3 = 1) ]]> diff --git a/lib/configfiles/rhel_centos.xml b/lib/configfiles/rhel_centos.xml index 7b8be4b8..6affbbd0 100644 --- a/lib/configfiles/rhel_centos.xml +++ b/lib/configfiles/rhel_centos.xml @@ -1774,7 +1774,7 @@ default_pass_scheme = CRYPT #password_query = \ # SELECT username, domain, password \ # FROM users WHERE username = '%n' AND domain = '%d' -password_query = SELECT username AS user, password_enc AS password, CONCAT(homedir, maildir) AS userdb_home, uid AS userdb_uid, gid AS userdb_gid, CONCAT('maildir:', homedir, maildir) AS userdb_mail, CONCAT('*:storage=', quota, 'M') as userdb_quota_rule FROM mail_users WHERE (username = '%u' OR email = '%u') AND ((imap = 1 AND '%Ls' = 'imap') OR (pop3 = 1 AND '%Ls' = 'pop3') OR '%Ls' = 'smtp' OR '%Ls' = 'sieve') +password_query = SELECT username AS user, password_enc AS password, CONCAT(homedir, maildir) AS userdb_home, uid AS userdb_uid, gid AS userdb_gid, CONCAT('maildir:', homedir, maildir) AS userdb_mail, CONCAT('*:storage=', quota, 'M') as userdb_quota_rule FROM mail_users WHERE (username = '%u' OR email = '%u') AND ((imap = 1 AND '%Ls' = 'imap') OR (pop3 = 1 AND '%Ls' = 'pop3') OR ((postfix = 'Y' AND '%Ls' = 'smtp') OR (postfix = 'Y' AND '%Ls' = 'sieve'))) #password_query = SELECT username as user, password, '/var/vmail/%d/%n' as userdb_home, 'maildir:/var/vmail/%d/%n' as userdb_mail, 150 as userdb_uid, 12 as userdb_gid FROM mailbox WHERE username = '%u' AND active = '1' # userdb query to retrieve the user information. It can return fields: diff --git a/lib/configfiles/stretch.xml b/lib/configfiles/stretch.xml index 235a0d4d..b3345e8d 100644 --- a/lib/configfiles/stretch.xml +++ b/lib/configfiles/stretch.xml @@ -2728,7 +2728,7 @@ user_query = SELECT CONCAT(homedir, maildir) AS home, CONCAT('maildir:', homedir # SELECT userid AS user, password, \ # home AS userdb_home, uid AS userdb_uid, gid AS userdb_gid \ # FROM users WHERE userid = '%u' -password_query = SELECT username AS user, password_enc AS password, CONCAT(homedir, maildir) AS userdb_home, uid AS userdb_uid, gid AS userdb_gid, CONCAT('maildir:', homedir, maildir) AS userdb_mail, CONCAT('*:storage=', quota, 'M') as userdb_quota_rule FROM mail_users WHERE (username = '%u' OR email = '%u') AND ((imap = 1 AND '%Ls' = 'imap') OR (pop3 = 1 AND '%Ls' = 'pop3') OR '%Ls' = 'smtp' OR '%Ls' = 'sieve') +password_query = SELECT username AS user, password_enc AS password, CONCAT(homedir, maildir) AS userdb_home, uid AS userdb_uid, gid AS userdb_gid, CONCAT('maildir:', homedir, maildir) AS userdb_mail, CONCAT('*:storage=', quota, 'M') as userdb_quota_rule FROM mail_users WHERE (username = '%u' OR email = '%u') AND ((imap = 1 AND '%Ls' = 'imap') OR (pop3 = 1 AND '%Ls' = 'pop3') OR ((postfix = 'Y' AND '%Ls' = 'smtp') OR (postfix = 'Y' AND '%Ls' = 'sieve'))) # Query to get a list of all usernames. #iterate_query = SELECT username AS user FROM users diff --git a/lib/configfiles/trusty.xml b/lib/configfiles/trusty.xml index d244c548..779db496 100644 --- a/lib/configfiles/trusty.xml +++ b/lib/configfiles/trusty.xml @@ -1048,7 +1048,7 @@ auth_mechanisms = plain login driver = mysql connect = host= dbname= user= password= default_pass_scheme = CRYPT -password_query = SELECT username AS user, password_enc AS password, CONCAT(homedir, maildir) AS userdb_home, uid AS userdb_uid, gid AS userdb_gid, CONCAT('maildir:', homedir, maildir) AS userdb_mail, CONCAT('*:storage=', quota, 'M') as userdb_quota_rule FROM mail_users WHERE (username = '%u' OR email = '%u') AND ((imap = 1 AND '%Ls' = 'imap') OR (pop3 = 1 AND '%Ls' = 'pop3') OR '%Ls' = 'smtp' OR '%Ls' = 'sieve') +password_query = SELECT username AS user, password_enc AS password, CONCAT(homedir, maildir) AS userdb_home, uid AS userdb_uid, gid AS userdb_gid, CONCAT('maildir:', homedir, maildir) AS userdb_mail, CONCAT('*:storage=', quota, 'M') as userdb_quota_rule FROM mail_users WHERE (username = '%u' OR email = '%u') AND ((imap = 1 AND '%Ls' = 'imap') OR (pop3 = 1 AND '%Ls' = 'pop3') OR ((postfix = 'Y' AND '%Ls' = 'smtp') OR (postfix = 'Y' AND '%Ls' = 'sieve'))) user_query = SELECT CONCAT(homedir, maildir) AS home, CONCAT('maildir:', homedir, maildir) AS mail, uid, gid, CONCAT('*:storage=', quota, 'M') as quota_rule FROM mail_users WHERE (username = '%u' OR email = '%u') iterate_query = SELECT username AS user FROM mail_users WHERE (imap = 1 OR pop3 = 1) ]]> diff --git a/lib/configfiles/xenial.xml b/lib/configfiles/xenial.xml index 34877c1f..35eadd70 100644 --- a/lib/configfiles/xenial.xml +++ b/lib/configfiles/xenial.xml @@ -2739,7 +2739,7 @@ user_query = SELECT CONCAT(homedir, maildir) AS home, CONCAT('maildir:', homedir # SELECT userid AS user, password, \ # home AS userdb_home, uid AS userdb_uid, gid AS userdb_gid \ # FROM users WHERE userid = '%u' -password_query = SELECT username AS user, password_enc AS password, CONCAT(homedir, maildir) AS userdb_home, uid AS userdb_uid, gid AS userdb_gid, CONCAT('maildir:', homedir, maildir) AS userdb_mail, CONCAT('*:storage=', quota, 'M') as userdb_quota_rule FROM mail_users WHERE (username = '%u' OR email = '%u') AND ((imap = 1 AND '%Ls' = 'imap') OR (pop3 = 1 AND '%Ls' = 'pop3') OR '%Ls' = 'smtp' OR '%Ls' = 'sieve') +password_query = SELECT username AS user, password_enc AS password, CONCAT(homedir, maildir) AS userdb_home, uid AS userdb_uid, gid AS userdb_gid, CONCAT('maildir:', homedir, maildir) AS userdb_mail, CONCAT('*:storage=', quota, 'M') as userdb_quota_rule FROM mail_users WHERE (username = '%u' OR email = '%u') AND ((imap = 1 AND '%Ls' = 'imap') OR (pop3 = 1 AND '%Ls' = 'pop3') OR ((postfix = 'Y' AND '%Ls' = 'smtp') OR (postfix = 'Y' AND '%Ls' = 'sieve'))) # Query to get a list of all usernames. #iterate_query = SELECT username AS user FROM users From 6161ad1bd3ad407a9723eed70ee00d4e1152c54b Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Wed, 19 Sep 2018 11:09:27 +0200 Subject: [PATCH 5/9] make CURLOPT_FOLLOWLOCATION optional in HttpClient::urlGet Signed-off-by: Michael Kaufmann --- lib/classes/cURL/class.HttpClient.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/classes/cURL/class.HttpClient.php b/lib/classes/cURL/class.HttpClient.php index 53a52114..6953624a 100644 --- a/lib/classes/cURL/class.HttpClient.php +++ b/lib/classes/cURL/class.HttpClient.php @@ -10,13 +10,15 @@ class HttpClient * * @return array */ - public static function urlGet($url) + public static function urlGet($url, $follow_location = true) { include FROXLOR_INSTALL_DIR . '/lib/version.inc.php'; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_USERAGENT, 'Froxlor/' . $version); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + if ($follow_location) { + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + } curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $output = curl_exec($ch); if ($output === false) { From 91195fda89a1ad08a4de3edeb751b80622bf6cca Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Wed, 19 Sep 2018 11:15:03 +0200 Subject: [PATCH 6/9] check for all needed php extensions and mysqldump tool in setup process, fixes #569 Signed-off-by: Michael Kaufmann --- install/lib/class.FroxlorInstall.php | 127 +++++++++++++-------------- install/lng/english.lng.php | 5 ++ install/lng/german.lng.php | 5 ++ 3 files changed, 70 insertions(+), 67 deletions(-) diff --git a/install/lib/class.FroxlorInstall.php b/install/lib/class.FroxlorInstall.php index 598bf66a..1430b33d 100644 --- a/install/lib/class.FroxlorInstall.php +++ b/install/lib/class.FroxlorInstall.php @@ -964,84 +964,39 @@ class FroxlorInstall } else { $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); } - + + // check for session-extension + $this->_requirementCheckFor($content, $_die, 'session', false, 'phpsession'); + + // check for ctype-extension + $this->_requirementCheckFor($content, $_die, 'ctype', false, 'phpctype'); + + // check for SimpleXML-extension + $this->_requirementCheckFor($content, $_die, 'simplexml', false, 'phpsimplexml'); + // check for xml-extension - $content .= $this->_status_message('begin', $this->_lng['requirements']['phpxml']); - - if (! extension_loaded('xml')) { - $content .= $this->_status_message('red', $this->_lng['requirements']['notinstalled']); - $_die = true; - } else { - $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); - } + $this->_requirementCheckFor($content, $_die, 'xml', false, 'phpxml'); // check for filter-extension - $content .= $this->_status_message('begin', $this->_lng['requirements']['phpfilter']); - - if (! extension_loaded('filter')) { - $content .= $this->_status_message('red', $this->_lng['requirements']['notinstalled']); - $_die = true; - } else { - $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); - } + $this->_requirementCheckFor($content, $_die, 'filter', false, 'phpfilter'); // check for posix-extension - $content .= $this->_status_message('begin', $this->_lng['requirements']['phpposix']); - - if (! extension_loaded('posix')) { - $content .= $this->_status_message('red', $this->_lng['requirements']['notinstalled']); - $_die = true; - } else { - $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); - } - - // check for bstring-extension - $content .= $this->_status_message('begin', $this->_lng['requirements']['phpmbstring']); - - if (! extension_loaded('mbstring')) { - $content .= $this->_status_message('red', $this->_lng['requirements']['notinstalled']); - $_die = true; - } else { - $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); - } + $this->_requirementCheckFor($content, $_die, 'posix', false, 'phpposix'); + + // check for mbstring-extension + $this->_requirementCheckFor($content, $_die, 'mbstring', false, 'phpmbstring'); // check for curl extension - $content .= $this->_status_message('begin', $this->_lng['requirements']['phpcurl']); - - if (! extension_loaded('curl')) { - $content .= $this->_status_message('red', $this->_lng['requirements']['notinstalled']); - $_die = true; - } else { - $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); - } - - // check for json extension - $content .= $this->_status_message('begin', $this->_lng['requirements']['phpjson']); + $this->_requirementCheckFor($content, $_die, 'curl', false, 'phpcurl'); - if (! extension_loaded('json')) { - $content .= $this->_status_message('red', $this->_lng['requirements']['notinstalled']); - $_die = true; - } else { - $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); - } + // check for json extension + $this->_requirementCheckFor($content, $_die, 'json', false, 'phpjson'); // check for bcmath extension - $content .= $this->_status_message('begin', $this->_lng['requirements']['phpbcmath']); - - if (! extension_loaded('bcmath')) { - $content .= $this->_status_message('orange', $this->_lng['requirements']['notinstalled'] . "
" . $this->_lng['requirements']['bcmathdescription']); - } else { - $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); - } + $this->_requirementCheckFor($content, $_die, 'bcmath', true, 'phpbcmath', 'bcmathdescription'); // check for zip extension - $content .= $this->_status_message('begin', $this->_lng['requirements']['phpzip']); - - if (! extension_loaded('zip')) { - $content .= $this->_status_message('orange', $this->_lng['requirements']['notinstalled'] . "
" . $this->_lng['requirements']['zipdescription']); - } else { - $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); - } + $this->_requirementCheckFor($content, $_die, 'zip', true, 'phpzip', 'zipdescription'); // check for open_basedir $content .= $this->_status_message('begin', $this->_lng['requirements']['openbasedir']); @@ -1051,6 +1006,16 @@ class FroxlorInstall } else { $content .= $this->_status_message('green', 'off'); } + + // check for mysqldump binary in order to backup existing database + $content .= $this->_status_message('begin', $this->_lng['requirements']['mysqldump']); + + if (file_exists("/usr/bin/mysqldump") || file_exists("/usr/local/bin/mysqldump")) { + $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); + } else { + $content .= $this->_status_message('orange', $this->_lng['requirements']['notinstalled'] . "
" . $this->_lng['requirements']['mysqldumpmissing']); + } + $content .= ""; // check if we have unrecoverable errors @@ -1073,6 +1038,22 @@ class FroxlorInstall 'pagenavigation' => $navigation ); } + + private function _requirementCheckFor(&$content, &$_die, $ext = '', $optional = false, $lng_txt = "", $lng_desc = "") + { + $content .= $this->_status_message('begin', $this->_lng['requirements'][$lng_txt]); + + if (! extension_loaded($ext)) { + if (!$optional) { + $content .= $this->_status_message('red', $this->_lng['requirements']['notinstalled']); + $_die = true; + } else { + $content .= $this->_status_message('orange', $this->_lng['requirements']['notinstalled'] . "
" . $this->_lng['requirements'][$lng_desc]); + } + } else { + $content .= $this->_status_message('green', $this->_lng['requirements']['installed']); + } + } /** * send no-caching headers and set the default timezone @@ -1141,12 +1122,24 @@ class FroxlorInstall } } - $lngfile = $this->_basepath . '/install/lng/' . $this->_activelng . '.lng.php'; + // require english base language as fallback + $lngfile = $this->_basepath . '/install/lng/english.lng.php'; if (file_exists($lngfile)) { // includes file /lng/$language.lng.php if it exists require $lngfile; $this->_lng = $lng; } + + // require chosen language if not english + if ($this->_activelng != 'english') + { + $lngfile = $this->_basepath . '/install/lng/' . $this->_activelng . '.lng.php'; + if (file_exists($lngfile)) { + // includes file /lng/$language.lng.php if it exists + require $lngfile; + $this->_lng = $lng; + } + } } /** diff --git a/install/lng/english.lng.php b/install/lng/english.lng.php index a4cea2dc..c1020569 100644 --- a/install/lng/english.lng.php +++ b/install/lng/english.lng.php @@ -28,6 +28,9 @@ $lng['requirements']['newerphpprefered'] = 'Good, but php-5.6 is prefered.'; $lng['requirements']['phpmagic_quotes_runtime'] = 'magic_quotes_runtime...'; $lng['requirements']['phpmagic_quotes_runtime_description'] = 'PHP setting "magic_quotes_runtime" must be set to "Off". We have disabled it temporary for now please fix the coresponding php.ini.'; $lng['requirements']['phppdo'] = 'PHP PDO extension and PDO-MySQL driver...'; +$lng['requirements']['phpsession'] = 'PHP session-extension...'; +$lng['requirements']['phpctype'] = 'PHP ctype-extension...'; +$lng['requirements']['phpsimplexml'] = 'PHP SimpleXML-extension...'; $lng['requirements']['phpxml'] = 'PHP XML-extension...'; $lng['requirements']['phpfilter'] = 'PHP filter-extension...'; $lng['requirements']['phpposix'] = 'PHP posix-extension...'; @@ -40,6 +43,8 @@ $lng['requirements']['bcmathdescription'] = 'Traffic-calculation related functio $lng['requirements']['zipdescription'] = 'The auto-update feature requires the zip extension.'; $lng['requirements']['openbasedir'] = 'open_basedir...'; $lng['requirements']['openbasedirenabled'] = 'Froxlor will not work properly with open_basedir enabled. Please disable open_basedir for Froxlor in the coresponding php.ini'; +$lng['requirements']['mysqldump'] = 'MySQL dump tool'; +$lng['requirements']['mysqldumpmissing'] = 'Automatic backup of possible existing database is not possible. Please install mysql-client tools'; $lng['requirements']['diedbecauseofrequirements'] = 'Cannot install Froxlor without these requirements! Try to fix them and retry.'; $lng['requirements']['froxlor_succ_checks'] = 'All requirements are satisfied'; diff --git a/install/lng/german.lng.php b/install/lng/german.lng.php index 4e4638db..49998936 100644 --- a/install/lng/german.lng.php +++ b/install/lng/german.lng.php @@ -28,6 +28,9 @@ $lng['requirements']['newerphpprefered'] = 'Passt, aber php-5.6 wird bevorzugt.' $lng['requirements']['phpmagic_quotes_runtime'] = 'magic_quotes_runtime'; $lng['requirements']['phpmagic_quotes_runtime_description'] = 'Die PHP Einstellung "magic_quotes_runtime" muss deaktiviert sein ("Off"). Die Einstellung wurde temporär deaktiviert, bitte ändern Sie diese in der entsprechenden php.ini.'; $lng['requirements']['phppdo'] = 'PHP PDO Erweiterung und PDO-MySQL Treiber...'; +$lng['requirements']['phpsession'] = 'PHP session-Erweiterung...'; +$lng['requirements']['phpctype'] = 'PHP ctype-Erweiterung...'; +$lng['requirements']['phpsimplexml'] = 'PHP SimpleXML-Erweiterung...'; $lng['requirements']['phpxml'] = 'PHP XML-Erweiterung...'; $lng['requirements']['phpfilter'] = 'PHP filter-Erweiterung...'; $lng['requirements']['phpposix'] = 'PHP posix-Erweiterung...'; @@ -40,6 +43,8 @@ $lng['requirements']['bcmathdescription'] = 'Traffic-Berechnungs bezogene Funkti $lng['requirements']['zipdescription'] = 'Die Auto-Update Funktion benötigt die zip Erweiterung.'; $lng['requirements']['openbasedir'] = 'open_basedir genutzt wird...'; $lng['requirements']['openbasedirenabled'] = 'Froxlor wird mit aktiviertem open_basedir nicht vollständig funktionieren. Bitte deaktivieren Sie open_basedir für Froxlor in der entsprechenden php.ini'; +$lng['requirements']['mysqldump'] = 'MySQL dump Tool'; +$lng['requirements']['mysqldumpmissing'] = 'Ein automatisches Backup einer möglicherweise schon existierenden Datenbank nicht möglich. Bitte mysql-client installieren'; $lng['requirements']['diedbecauseofrequirements'] = 'Kann Froxlor ohne diese Voraussetzungen nicht installieren! Beheben Sie die angezeigten Probleme und versuchen Sie es erneut.'; $lng['requirements']['froxlor_succ_checks'] = 'Alle Vorraussetzungen sind erfüllt'; From f0edf97ac7edc52f9918cc700f592e3201246d0b Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Wed, 19 Sep 2018 11:15:42 +0200 Subject: [PATCH 7/9] do not follow url location/redirect when using LE selfcheck Signed-off-by: Michael Kaufmann --- lib/classes/ssl/class.lescript.php | 2 +- lib/classes/ssl/class.lescript_v2.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/classes/ssl/class.lescript.php b/lib/classes/ssl/class.lescript.php index cdc848f6..1f720336 100644 --- a/lib/classes/ssl/class.lescript.php +++ b/lib/classes/ssl/class.lescript.php @@ -216,7 +216,7 @@ class lescript // simple self check if (Settings::Get('system.disable_le_selfcheck') == '0') { - $selfcheckpayload = HttpClient::urlGet($uri); + $selfcheckpayload = HttpClient::urlGet($uri, false); if ($payload !== trim($selfcheckpayload)) { $errmsg = json_encode(error_get_last()); if ($errmsg != "null") { diff --git a/lib/classes/ssl/class.lescript_v2.php b/lib/classes/ssl/class.lescript_v2.php index 4469546b..6216ac10 100644 --- a/lib/classes/ssl/class.lescript_v2.php +++ b/lib/classes/ssl/class.lescript_v2.php @@ -233,7 +233,7 @@ class lescript_v2 // simple self check if (Settings::Get('system.disable_le_selfcheck') == '0') { - $selfcheckpayload = HttpClient::urlGet($uri); + $selfcheckpayload = HttpClient::urlGet($uri, false); if ($payload !== trim($selfcheckpayload)) { $errmsg = json_encode(error_get_last()); if ($errmsg != "null") { From 9e289a23806273eb8ebd65ed73de462e593fea1e Mon Sep 17 00:00:00 2001 From: Markus Petermann Date: Tue, 18 Sep 2018 02:20:17 +0200 Subject: [PATCH 8/9] Fixed class.lescript_v2.php to work with ACMEv2 Account(kid) needs to be saved for future requests. Install/Update part is untested. --- install/froxlor.sql | 4 +++- install/updates/froxlor/0.9/update_0.9.inc.php | 13 +++++++++++++ lib/classes/ssl/class.lescript_v2.php | 6 +++++- scripts/jobs/cron_letsencrypt_v2.php | 2 ++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/install/froxlor.sql b/install/froxlor.sql index c00a730f..80091911 100644 --- a/install/froxlor.sql +++ b/install/froxlor.sql @@ -198,6 +198,7 @@ CREATE TABLE `panel_customers` ( `lepublickey` mediumtext default NULL, `leprivatekey` mediumtext default NULL, `leregistered` tinyint(1) NOT NULL default '0', + `leaccount` varchar(255) default '', `allowed_phpconfigs` varchar(500) NOT NULL default '', PRIMARY KEY (`customerid`), UNIQUE KEY `loginname` (`loginname`) @@ -653,6 +654,7 @@ opcache.interned_strings_buffer'), ('system', 'hsts_incsub', '0'), ('system', 'hsts_preload', '0'), ('system', 'leregistered', '0'), + ('system', 'leaccount', ''), ('system', 'nssextrausers', '0'), ('system', 'disable_le_selfcheck', '0'), ('system', 'ssl_protocols', 'TLSv1,TLSv1.2'), @@ -692,7 +694,7 @@ opcache.interned_strings_buffer'), ('panel', 'password_special_char', '!?<>§$%+#=@'), ('panel', 'customer_hide_options', ''), ('panel', 'version', '0.9.39.5'), - ('panel', 'db_version', '201805290'); + ('panel', 'db_version', '201809180'); DROP TABLE IF EXISTS `panel_tasks`; diff --git a/install/updates/froxlor/0.9/update_0.9.inc.php b/install/updates/froxlor/0.9/update_0.9.inc.php index 17432b94..6b07bc11 100644 --- a/install/updates/froxlor/0.9/update_0.9.inc.php +++ b/install/updates/froxlor/0.9/update_0.9.inc.php @@ -3998,3 +3998,16 @@ if (isDatabaseVersion('201805241')) { updateToDbVersion('201805290'); } } + +if (isDatabaseVersion('201805290')) { + + showUpdateStep("Adding leaccount field to panel customers"); + Database::query("ALTER TABLE `" . TABLE_PANEL_CUSTOMERS . "` ADD COLUMN `leaccount` varchar(255) default '' AFTER `leregistered`;"); + lastStepStatus(0); + + showUpdateStep("Adding system setting for let's-encrypt account"); + Settings::AddNew('system.leaccount', ""); + lastStepStatus(0); + + updateToDbVersion('201809180'); +} diff --git a/lib/classes/ssl/class.lescript_v2.php b/lib/classes/ssl/class.lescript_v2.php index 2874ef34..7c51f049 100644 --- a/lib/classes/ssl/class.lescript_v2.php +++ b/lib/classes/ssl/class.lescript_v2.php @@ -76,6 +76,7 @@ class lescript_v2 $this->customerId = (! $isFroxlorVhost ? $certrow['customerid'] : null); $this->isFroxlorVhost = $isFroxlorVhost; $this->isLeProduction = (Settings::Get('system.letsencryptca') == 'production'); + $this->_acc_location = $certrow['leaccount']; $leregistered = $certrow['leregistered']; @@ -166,6 +167,7 @@ class lescript_v2 if ($this->client->getLastCode() == 403) { $this->log("Got status 403 - setting LE status to unregistered."); + $this->_acc_location = ''; $this->setLeRegisteredState(0); throw new RuntimeException("Got 'unauthorized' response - we need to re-register at next run. Whole response: " . json_encode($response)); } @@ -347,10 +349,12 @@ class lescript_v2 if ($this->isLeProduction) { if ($this->isFroxlorVhost) { Settings::Set('system.leregistered', $state); + Settings::Set('system.leaccount', $this->_acc_location); } else { - $upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `leregistered` = :registered " . "WHERE `customerid` = :customerid;"); + $upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `leregistered` = :registered, `leaccount` = :kid " . "WHERE `customerid` = :customerid;"); Database::pexecute($upd_stmt, array( 'registered' => $state, + 'kid' => $this->_acc_location, 'customerid' => $this->customerId )); } diff --git a/scripts/jobs/cron_letsencrypt_v2.php b/scripts/jobs/cron_letsencrypt_v2.php index c56a3e4e..c476c11b 100644 --- a/scripts/jobs/cron_letsencrypt_v2.php +++ b/scripts/jobs/cron_letsencrypt_v2.php @@ -45,6 +45,7 @@ $certificates_stmt = Database::query(" cust.`leprivatekey`, cust.`lepublickey`, cust.`leregistered`, + cust.`leaccount`, cust.`customerid`, cust.`loginname` FROM @@ -109,6 +110,7 @@ if (Settings::Get('system.le_froxlor_enabled') == '1') { 'leprivatekey' => Settings::Get('system.leprivatekey'), 'lepublickey' => Settings::Get('system.lepublickey'), 'leregistered' => Settings::Get('system.leregistered'), + 'leaccount' => Settings::Get('system.leaccount'), 'ssl_redirect' => Settings::Get('system.le_froxlor_redirect'), 'expirationdate' => null, 'ssl_cert_file' => null, From bd036a0fde01ba219d67048e1f3a9946b4084a89 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Tue, 25 Sep 2018 08:12:21 +0200 Subject: [PATCH 9/9] remove unnecessary string-break; use standardlanguage-variable as fallback language for installer Signed-off-by: Michael Kaufmann --- install/lib/class.FroxlorInstall.php | 4 ++-- lib/classes/ssl/class.lescript.php | 4 ++-- lib/classes/ssl/class.lescript_v2.php | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/install/lib/class.FroxlorInstall.php b/install/lib/class.FroxlorInstall.php index 1430b33d..6895df63 100644 --- a/install/lib/class.FroxlorInstall.php +++ b/install/lib/class.FroxlorInstall.php @@ -1123,7 +1123,7 @@ class FroxlorInstall } // require english base language as fallback - $lngfile = $this->_basepath . '/install/lng/english.lng.php'; + $lngfile = $this->_basepath . '/install/lng/' . $standardlanguage . '.lng.php'; if (file_exists($lngfile)) { // includes file /lng/$language.lng.php if it exists require $lngfile; @@ -1131,7 +1131,7 @@ class FroxlorInstall } // require chosen language if not english - if ($this->_activelng != 'english') + if ($this->_activelng != $standardlanguage) { $lngfile = $this->_basepath . '/install/lng/' . $this->_activelng . '.lng.php'; if (file_exists($lngfile)) { diff --git a/lib/classes/ssl/class.lescript.php b/lib/classes/ssl/class.lescript.php index 1f720336..acf80e86 100644 --- a/lib/classes/ssl/class.lescript.php +++ b/lib/classes/ssl/class.lescript.php @@ -82,7 +82,7 @@ class lescript Settings::Set('system.leprivatekey', $keys['private']); Settings::Set('system.leregistered', 0); // key is not registered } else { - $upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `lepublickey` = :public, `leprivatekey` = :private, `leregistered` = :registered " . "WHERE `customerid` = :customerid;"); + $upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `lepublickey` = :public, `leprivatekey` = :private, `leregistered` = :registered WHERE `customerid` = :customerid;"); Database::pexecute($upd_stmt, array( 'public' => $keys['public'], 'private' => $keys['private'], @@ -342,7 +342,7 @@ class lescript if ($this->isFroxlorVhost) { Settings::Set('system.leregistered', $state); } else { - $upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `leregistered` = :registered " . "WHERE `customerid` = :customerid;"); + $upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `leregistered` = :registered WHERE `customerid` = :customerid;"); Database::pexecute($upd_stmt, array( 'registered' => $state, 'customerid' => $this->customerId diff --git a/lib/classes/ssl/class.lescript_v2.php b/lib/classes/ssl/class.lescript_v2.php index c4ae7df4..9bb135c0 100644 --- a/lib/classes/ssl/class.lescript_v2.php +++ b/lib/classes/ssl/class.lescript_v2.php @@ -94,7 +94,7 @@ class lescript_v2 Settings::Set('system.leprivatekey', $keys['private']); Settings::Set('system.leregistered', 0); // key is not registered } else { - $upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `lepublickey` = :public, `leprivatekey` = :private, `leregistered` = :registered " . "WHERE `customerid` = :customerid;"); + $upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `lepublickey` = :public, `leprivatekey` = :private, `leregistered` = :registered WHERE `customerid` = :customerid;"); Database::pexecute($upd_stmt, array( 'public' => $keys['public'], 'private' => $keys['private'], @@ -351,7 +351,7 @@ class lescript_v2 Settings::Set('system.leregistered', $state); Settings::Set('system.leaccount', $this->_acc_location); } else { - $upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `leregistered` = :registered, `leaccount` = :kid " . "WHERE `customerid` = :customerid;"); + $upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `leregistered` = :registered, `leaccount` = :kid WHERE `customerid` = :customerid;"); Database::pexecute($upd_stmt, array( 'registered' => $state, 'kid' => $this->_acc_location,