actionConfigureHttps(); break; case 'password': User::assertPermission("edit.password"); $this->actionShowHidePassword(); break; case 'customization': User::assertPermission("edit.design"); $this->actionCustomization(); break; case 'https-api-key-generate': case 'https-api-key-delete': User::assertPermission("edit.https"); $this->handleApiKey(substr($action, 14)); break; default: if ($action !== null) { Message::addWarning('main.invalid-action', $action); } } if (Request::isPost()) { Util::redirect('?do=webinterface'); } User::assertPermission('access-page'); } private function actionConfigureHttps() { $this->setRedirectFromPost(); $mode = Request::post('mode'); switch ($mode) { 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->updateHttpsRedirectModeOnly(); break; } if ($mode !== 'off') { Property::set(WebInterface::PROP_HSTS, Request::post('usehsts', false, 'string') === 'on' ? 'True' : 'False'); WebInterface::setDomainRedirect(Request::post('redirdomain', false, 'string') === 'on'); } if ($taskId !== null) { Session::set('https-id', $taskId, 1); Util::redirect('?do=WebInterface&show=httpsupdate'); } Util::redirect('?do=WebInterface'); } private function actionShowHidePassword() { Property::setPasswordFieldType(Request::post('mode') === 'show' ? 'text' : 'password'); Util::redirect('?do=WebInterface'); } private function actionCustomization() { $prefix = Request::post('prefix', '', 'string'); if (!empty($prefix) && !preg_match('/[)}\]\-_\s&$!\/+*^>]$/', $prefix)) { $prefix .= ' '; } Property::set('page-title-prefix', $prefix); Property::set('logo-background', Request::post('bgcolor', '', 'string')); Util::redirect('?do=WebInterface'); } protected function doRender() { Render::addTemplate("heading"); // // HTTPS // if (Request::get('show') === 'httpsupdate') { Render::addTemplate('httpd-restart', array('taskid' => Session::get('https-id'))); } $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' : ''), 'redirdomain_checked' => ($redirdomain ? 'checked' : ''), ); // 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 if ($https) { // User is using https, just warn to prevent lockout Message::addWarning('https-want-off-is-used'); } else { // User is not using https, try to delete stray certificate $this->setHttpsOff(); } } elseif ($https) { // Set to off, no cert found, but still using HTTPS apparently // Admin might have modified web server config in another way Message::addWarning('https-used-without-cert'); } } elseif ($type === 'generated' || $type === 'supplied' || $type === 'acme' || $type === 'api') { $data['httpsEnabled'] = true; if ($force && !$https) { Message::addWarning('https-want-redirect-is-plain'); } if (!$exists) { Message::addWarning('https-on-cert-missing'); } } else { // 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; if (User::hasPermission("edit.https")) { $data['acmeKeyId'] = Acme::getKeyId(); $data['acmeHmacKey'] = Acme::getHmacKey(); $data['httpsApiKey'] = WebInterface::getApiKey(); } // $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); } } $data['httpsApiKeyPostUrl'] = ($https ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . '/slx-admin/api.php?do=webinterface'; Permission::addGlobalTags($data['perms'], null, ['edit.https']); Render::addTemplate('https', $data); // // Password fields // $data = array(); if (Property::getPasswordFieldType() === 'text') { $data['selected_show'] = 'checked'; } else { $data['selected_hide'] = 'checked'; } Permission::addGlobalTags($data['perms'], null, ['edit.password']); Render::addTemplate('passwords', $data); // // Colors/Prefix // $data = array('prefix' => Property::get('page-title-prefix')); $data['colors'] = array_map(function ($i) { return array('color' => $i ? '#' . $i : '', 'text' => Render::readableColor($i)); }, array('', 'f00', '0f0', '00f', 'ff0', 'f0f', '0ff', 'fff', '000', 'f90', '09f', '90f', 'f09', '9f0')); $color = Property::get('logo-background'); foreach ($data['colors'] as &$c) { if ($c['color'] === $color) { $c['selected'] = 'selected'; $color = false; break; } } unset($c); if ($color) { $data['colors'][] = array('color' => $color, 'selected' => 'selected'); } Permission::addGlobalTags($data['perms'], null, ['edit.design']); Render::addTemplate('customization', $data); } private function setRedirectFromPost(): void { $force = Request::post('httpsredirect', false, 'string') === 'on'; Property::set(WebInterface::PROP_REDIRECT, $force ? 'True' : 'False'); } private function updateHttpsRedirectModeOnly(): ?string { return WebInterface::tmSetHttpRedirectMode(); } private function setHttpsOff(): ?string { Header('Strict-Transport-Security: max-age=0', true); Session::deleteCookie(); return WebInterface::tmDisableHttps(); } private function setHttpsRandomCert(): ?string { return WebInterface::tmGenerateRandomCert(); } private function setHttpsCustomCert(): ?string { $cert = trim(Request::post('certificate', Request::REQUIRED, 'string')); $key = trim(Request::post('privatekey', Request::REQUIRED, 'string')); $chain = trim(Request::post('cachain', '', 'string')); if (!empty($chain)) { $cert .= "\n" . $chain; } return WebInterface::tmImportCustomCert($key . "\n", $cert . "\n", 'supplied', 'New certificate uploaded by ' . User::getLogin()); } private function setAcmeMode(): ?string { Property::set(WebInterface::PROP_TYPE, 'acme'); $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 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 handleApiKey(string $substr) { if ($substr === 'generate') { WebInterface::setApiKey(Util::randomUuid()); } elseif ($substr === 'delete') { WebInterface::setApiKey(null); } } }