diff options
Diffstat (limited to 'modules-available/webinterface/page.inc.php')
-rw-r--r-- | modules-available/webinterface/page.inc.php | 184 |
1 files changed, 139 insertions, 45 deletions
diff --git a/modules-available/webinterface/page.inc.php b/modules-available/webinterface/page.inc.php index ca52c2ab..35f21b38 100644 --- a/modules-available/webinterface/page.inc.php +++ b/modules-available/webinterface/page.inc.php @@ -3,10 +3,6 @@ class Page_WebInterface extends Page { - const PROP_REDIRECT = 'webinterface.https-redirect'; - const PROP_TYPE = 'webinterface.https-type'; - const PROP_HSTS = 'webinterface.https-hsts'; - protected function doPreprocess() { User::load(); @@ -27,6 +23,7 @@ class Page_WebInterface extends Page User::assertPermission("edit.design"); $this->actionCustomization(); break; + default: } if (Request::isPost()) { Util::redirect('?do=webinterface'); @@ -38,24 +35,28 @@ class Page_WebInterface extends Page { $mode = Request::post('mode'); switch ($mode) { - case 'off': - $task = $this->setHttpsOff(); - break; - case 'random': - $task = $this->setHttpsRandomCert(); - break; - case 'custom': - $task = $this->setHttpsCustomCert(); - break; - default: - $task = $this->setRedirectMode(); - break; + case 'off': + $taskId = $this->setHttpsOff(); + break; + case 'random': + $taskId = $this->setHttpsRandomCert(); + break; + case 'custom': + $taskId = $this->setHttpsCustomCert(); + break; + case 'acme': + $taskId = $this->setAcmeMode(); + break; + default: + $taskId = $this->setRedirectMode(); + break; } if ($mode !== 'off') { - Property::set(self::PROP_HSTS, Request::post('usehsts', false, 'string') === 'on' ? 'True' : 'False'); + Property::set(WebInterface::PROP_HSTS, Request::post('usehsts', false, 'string') === 'on' ? 'True' : 'False'); + WebInterface::setDomainRedirect(Request::post('redirdomain', false, 'string') === 'on'); } - if (isset($task['id'])) { - Session::set('https-id', $task['id']); + if ($taskId !== null) { + Session::set('https-id', $taskId, 1); Util::redirect('?do=WebInterface&show=httpsupdate'); } Util::redirect('?do=WebInterface'); @@ -70,7 +71,7 @@ class Page_WebInterface extends Page private function actionCustomization() { $prefix = Request::post('prefix', '', 'string'); - if (!empty($prefix) && !preg_match('/[\]\)\}\-_\s\&\$\!\/\+\*\^\>]$/', $prefix)) { + if (!empty($prefix) && !preg_match('/[)}\]\-_\s&$!\/+*^>]$/', $prefix)) { $prefix .= ' '; } Property::set('page-title-prefix', $prefix); @@ -87,17 +88,25 @@ class Page_WebInterface extends Page if (Request::get('show') === 'httpsupdate') { Render::addTemplate('httpd-restart', array('taskid' => Session::get('https-id'))); } - $type = Property::get(self::PROP_TYPE); - $force = Property::get(self::PROP_REDIRECT) === 'True'; - $hsts = Property::get(self::PROP_HSTS) === 'True'; + $type = Property::get(WebInterface::PROP_TYPE); + $force = Property::get(WebInterface::PROP_REDIRECT) === 'True'; + $hsts = Property::get(WebInterface::PROP_HSTS) === 'True'; + $redirdomain = WebInterface::getDomainRedirect(); $https = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'; $exists = file_exists('/etc/lighttpd/server.pem'); $data = array( 'httpsUsed' => $https, 'redirect_checked' => ($force ? 'checked' : ''), - 'hsts_checked' => ($hsts ? 'checked' : '') + 'hsts_checked' => ($hsts ? 'checked' : ''), + 'redirdomain_checked' => ($redirdomain ? 'checked' : ''), ); - // Type should be 'off', 'generated', 'supplied' + // Type should be 'off', 'generated', 'supplied' or 'acme' + if ($type === 'acme') { + $err = Acme::getLastError(); + if (!empty($err)) { + Render::addTemplate('acme-error', ['error' => $err]); + } + } if ($type === 'off') { if ($exists) { // HTTPS is set to off, but a certificate exists @@ -113,7 +122,7 @@ class Page_WebInterface extends Page // Admin might have modified web server config in another way Message::addWarning('https-used-without-cert'); } - } elseif ($type === 'generated' || $type === 'supplied') { + } elseif ($type === 'generated' || $type === 'supplied' || $type === 'acme') { $data['httpsEnabled'] = true; if ($force && !$https) { Message::addWarning('https-want-redirect-is-plain'); @@ -125,21 +134,59 @@ class Page_WebInterface extends Page // Unknown config - maybe upgraded old install that doesn't keep track if ($exists || $https) { $type = 'unknown'; // Legacy fallback + $data['httpsEnabled'] = true; } else { $type = 'off'; } } + $domains = implode("\n", Acme::getDomains()); + if (empty($domains)) { + $domains = $_SERVER['HTTP_HOST']; + } + $data['acmeProviders'] = []; + foreach (Acme::getProviders() as $id => $name) { + $data['acmeProviders'][] = [ + 'id' => $id, + 'name' => $name, + 'selected' => $id === Acme::getProvider() ? 'selected' : '', + ]; + } + $data['acmeMail'] = Acme::getMail(); + $data['acmeDomains'] = $domains; + $data['acmeKeyId'] = Acme::getKeyId(); + $data['acmeHmacKey'] = Acme::getHmacKey(); + // $type might have changed in above block $data[$type . 'Selected'] = true; + // Show cert info if possible + if ($type !== 'off') { + $data['certDomains'] = []; + $exp = 0; + $iss = ''; + if (WebInterface::extractCurrentCertData($data['certDomains'], $exp, $iss)) { + $data['certExpire'] = Util::prettyTime($exp); + $data['certIssuer'] = $iss; + $diff = $exp - time(); + $class = []; + if ($diff < 86400 * 3) { + $class[] = 'text-danger'; + } + if ($diff < 86400 * 10) { + $class[] = 'slx-bold'; + } + $data['certExpireClass'] = implode(' ', $class); + } + } Permission::addGlobalTags($data['perms'], null, ['edit.https']); Render::addTemplate('https', $data); // // Password fields // $data = array(); - if (Property::getPasswordFieldType() === 'text') + if (Property::getPasswordFieldType() === 'text') { $data['selected_show'] = 'checked'; - else + } else { $data['selected_hide'] = 'checked'; + } Permission::addGlobalTags($data['perms'], null, ['edit.password']); Render::addTemplate('passwords', $data); // @@ -164,52 +211,99 @@ class Page_WebInterface extends Page Render::addTemplate('customization', $data); } - private function setHttpsOff() + private function setHttpsOff(): ?string { - Property::set(self::PROP_TYPE, 'off'); - Property::set(self::PROP_HSTS, 'off'); + Property::set(WebInterface::PROP_TYPE, 'off'); + Property::set(WebInterface::PROP_HSTS, 'off'); Header('Strict-Transport-Security: max-age=0', true); Session::deleteCookie(); - return Taskmanager::submit('LighttpdHttps', array()); + $task = Taskmanager::submit('LighttpdHttps', array()); + return $task['id'] ?? null; } - private function setHttpsRandomCert() + private function setHttpsRandomCert(): ?string { $force = Request::post('httpsredirect', false, 'string') === 'on'; - Property::set(self::PROP_TYPE, 'generated'); - Property::set(self::PROP_REDIRECT, $force ? 'True' : 'False'); - return Taskmanager::submit('LighttpdHttps', array( + Property::set(WebInterface::PROP_TYPE, 'generated'); + Property::set(WebInterface::PROP_REDIRECT, $force ? 'True' : 'False'); + $task = Taskmanager::submit('LighttpdHttps', array( 'proxyip' => Property::getServerIp(), 'redirect' => $force, )); + return $task['id'] ?? null; } - private function setHttpsCustomCert() + private function setHttpsCustomCert(): ?string { $force = Request::post('httpsredirect', false, 'string') === 'on'; - Property::set(self::PROP_TYPE, 'supplied'); - Property::set(self::PROP_REDIRECT, $force ? 'True' : 'False'); - return Taskmanager::submit('LighttpdHttps', array( + Property::set(WebInterface::PROP_TYPE, 'supplied'); + Property::set(WebInterface::PROP_REDIRECT, $force ? 'True' : 'False'); + $task = Taskmanager::submit('LighttpdHttps', array( 'importcert' => Request::post('certificate', 'bla'), 'importkey' => Request::post('privatekey', 'bla'), 'importchain' => Request::post('cachain', ''), 'redirect' => $force, )); + return $task['id'] ?? null; + } + + private function setAcmeMode(): ?string + { + $force = Request::post('httpsredirect', false, 'string') === 'on'; + Property::set(WebInterface::PROP_TYPE, 'acme'); + Property::set(WebInterface::PROP_REDIRECT, $force ? 'True' : 'False'); + $wipeAll = Request::post('acme-wipe-all', false, 'bool'); + // Get params + $provider = Request::post('acme-provider', Request::REQUIRED, 'string'); + $mail = Request::post('acme-mail', Request::REQUIRED, 'string'); + $domains = Request::post('acme-domains', Request::REQUIRED, 'string'); + $kid = Request::post('acme-kid', null, 'string'); + $hmac = Request::post('acme-hmac-key', null, 'string'); + // Check domains + $domains = preg_split('/[\r\n\s]+/', strtolower($domains), 0, PREG_SPLIT_NO_EMPTY); + $err = false; + foreach ($domains as $domain) { + if (!preg_match('/^[a-z0-9_.-]+$/', $domain)) { + Message::addError('invalid-domain', $domain); + $err = true; + } + } + unset($domain); + if ($err) + return null; + // First, try to revive existing config/certs if parameters didn't change + if (!$wipeAll + && $provider === Acme::getProvider() + && $mail === Acme::getMail() + && $kid === Acme::getKeyId() + && $hmac === Acme::getHmacKey() + && count($domains) === count(Acme::getDomains()) + && empty(array_diff($domains, Acme::getDomains()))) { + if (Acme::tryEnable()) + return null; // Nothing to do, old setup works + error_log('FUUUU'); + return Acme::renew(); // Hope for the best, otherwise user needs to check "force reissue" + } + if (!Acme::setConfig($provider, $mail, $kid, $hmac)) + return null; // Will generate error messages in this case + Acme::setDomains($domains); + return Acme::issueNewCertificate($wipeAll); } - private function setRedirectMode() + private function setRedirectMode(): ?string { $force = Request::post('httpsredirect', false, 'string') === 'on'; - Property::set(self::PROP_REDIRECT, $force ? 'True' : 'False'); - if (Property::get(self::PROP_TYPE) === 'off') { + Property::set(WebInterface::PROP_REDIRECT, $force ? 'True' : 'False'); + if (Property::get(WebInterface::PROP_TYPE) === 'off') { // Don't bother running the task if https isn't enabled - just // update the state in DB - return false; + return null; } - return Taskmanager::submit('LighttpdHttps', array( + $task = Taskmanager::submit('LighttpdHttps', array( 'redirectOnly' => true, 'redirect' => $force, )); + return $task['id'] ?? null; } } |