<?php
class Page_WebInterface extends Page
{
protected function doPreprocess()
{
User::load();
if (!User::isLoggedIn()) {
Message::addError('main.no-permission');
Util::redirect('?do=Main');
}
$action = Request::post('action', null, 'string');
switch ($action) {
case 'https':
User::assertPermission("edit.https");
$this->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 'random':
case 'off':
$taskId = $this->setHttpsOff();
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&mode=' . $mode);
}
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', [
'taskid' => Session::get('https-id'),
'mode' => Request::get('mode', '', 'string'),
]);
}
$type = Property::get(WebInterface::PROP_TYPE);
if ($type === 'off') {
// Not really possible anymore to disable HTTPS since we use it for client communication
$type = 'generated';
Property::set(WebInterface::PROP_TYPE, $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 === 'generated' || $type === 'supplied' || $type === 'acme' || $type === 'api') {
if ($force && !$https) {
Message::addWarning('https-want-redirect-is-plain');
}
if ($type !== 'generated' && !$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
} else {
$type = 'generated';
}
}
$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
$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
{
return WebInterface::tmDisableHttps();
}
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',
'Applying uploaded HTTPS certificate');
}
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);
}
}
}