<?php
class Page_WebInterface extends Page
{
protected function doPreprocess()
{
User::load();
if (!User::isLoggedIn()) {
Message::addError('main.no-permission');
Util::redirect('?do=Main');
}
switch (Request::post('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;
default:
}
if (Request::isPost()) {
Util::redirect('?do=webinterface');
}
User::assertPermission('access-page');
}
private function actionConfigureHttps()
{
$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->setRedirectMode();
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') {
$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;
$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') {
$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 setHttpsOff(): ?string
{
Property::set(WebInterface::PROP_TYPE, 'off');
Property::set(WebInterface::PROP_HSTS, 'off');
Header('Strict-Transport-Security: max-age=0', true);
Session::deleteCookie();
$task = Taskmanager::submit('LighttpdHttps', array());
return $task['id'] ?? null;
}
private function setHttpsRandomCert(): ?string
{
$force = Request::post('httpsredirect', false, 'string') === 'on';
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(): ?string
{
$force = Request::post('httpsredirect', false, 'string') === 'on';
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(): ?string
{
$force = Request::post('httpsredirect', false, 'string') === 'on';
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 null;
}
$task = Taskmanager::submit('LighttpdHttps', array(
'redirectOnly' => true,
'redirect' => $force,
));
return $task['id'] ?? null;
}
}