<?php
class WebInterface
{
public const PROP_TYPE = 'webinterface.https-type';
public const PROP_HSTS = 'webinterface.https-hsts';
public const PROP_REDIRECT = 'webinterface.https-redirect';
public const PROP_CURRENT_CERT_DOMAINS = 'webinterface.https-domains';
public const PROP_REDIRECT_DOMAIN = 'webinterface.redirect-domain';
public const PROP_API_KEY = 'webinterface.api-key';
/**
* Read data all handled domains from current certificate.
* SAN takes precedence, if empty, we fall back to CN.
* @param string[] $certDomains
* @return bool success reading?
*/
public static function extractCurrentCertData(array &$certDomains, int &$expireTimestamp, string &$issuer): bool
{
if (!is_readable('/etc/lighttpd/pub-cert.pem'))
return false;
$cert = openssl_x509_parse(file_get_contents('/etc/lighttpd/pub-cert.pem'));
if ($cert === false)
return false;
// Domains
$certDomains = [];
if (isset($cert['extensions']['subjectAltName'])) {
$doms = preg_split('/[,\s]+/', $cert['extensions']['subjectAltName'], -1, PREG_SPLIT_NO_EMPTY);
foreach ($doms as $d) {
if (substr_compare($d, 'DNS:', 0, 4, true) !== 0)
continue;
$d = substr($d, 4);
if (preg_match('/^([a-z0-9_-]|\*\.)[a-z0-9_.-]+$/', $d) && !in_array($d, $certDomains)) {
$certDomains[] = $d;
}
}
}
if (empty($certDomains) && isset($cert['subject']['CN'])
&& preg_match('/^([a-z0-9_-]|\*\.)[a-z0-9_.-]+$/', $cert['subject']['CN'])) {
$certDomains[] = $cert['subject']['CN'];
}
foreach ($certDomains as &$d) {
if ($d[-1] === '.') {
$d = substr($d, 0, -1);
}
}
Property::set(self::PROP_CURRENT_CERT_DOMAINS, implode(' ', $certDomains));
// Expire time
$expireTimestamp = $cert['validTo_time_t'] ?? 0;
// Issuer
$issuer = $cert['issuer']['CN'] ?? 'Unknown';
return true;
}
public static function setDomainRedirect(bool $enable): void
{
Property::set(self::PROP_REDIRECT_DOMAIN, $enable ? '1' : false);
}
public static function getDomainRedirect(): bool
{
return !empty(Property::get(self::PROP_REDIRECT_DOMAIN, false));
}
public static function isHttpsRedirectEnabled(): bool
{
return Property::get(self::PROP_REDIRECT) === 'True';
}
private static function registerCallback($task, string $newState, string $logMessage): void
{
if (!Taskmanager::isTask($task))
return;
TaskmanagerCallback::addCallback($task, 'webifCert', [
'state' => $newState,
'message' => $logMessage . ' by ' . (User::getLogin() ?? 'system'),
]);
}
public static function certTaskFinishedCallback(array $task, $data): void
{
if (!Taskmanager::isFinished($task))
return;
if (!isset($data['state']) || !isset($data['message'])) {
error_log('Invalid certTaskFinishedCallback: Missing fields');
return;
}
if (Taskmanager::isFailed($task)) {
EventLog::failure($data['message'], json_encode($task, JSON_PRETTY_PRINT));
return;
}
EventLog::info($data['message']);
Property::set(self::PROP_TYPE, $data['state']);
}
public static function tmDisableHttps(): ?string
{
Property::set(WebInterface::PROP_HSTS, 'off');
$task = Taskmanager::submit('LighttpdHttps', []);
self::registerCallback($task, 'off', 'HTTPS disabled');
return $task['id'] ?? null;
}
public static function tmGenerateRandomCert(): ?string
{
$task = Taskmanager::submit('LighttpdHttps', [
'proxyip' => Property::getServerIp(),
'redirect' => self::isHttpsRedirectEnabled(),
]);
self::registerCallback($task, 'generated', 'Self-signed HTTPS certificate generated');
return $task['id'] ?? null;
}
public static function tmImportCustomCert(string $key, string $cert, string $type, string $logMessage): ?string
{
$key = preg_replace('/[\r\n]+/', "\n", $key);
$cert = preg_replace('/[\r\n]+/', "\n", $cert);
Property::set(WebInterface::PROP_TYPE, $type);
$task = Taskmanager::submit('LighttpdHttps', [
'importcert' => $cert,
'importkey' => $key,
'redirect' => self::isHttpsRedirectEnabled(),
]);
self::registerCallback($task, $type, $logMessage);
return $task['id'] ?? null;
}
public static function tmSetHttpRedirectMode(): ?string
{
if (Property::get(WebInterface::PROP_TYPE) === 'off')
return null;
$task = Taskmanager::submit('LighttpdHttps', array(
'redirectOnly' => true,
'redirect' => self::isHttpsRedirectEnabled(),
));
return $task['id'] ?? null;
}
public static function getApiKey(): ?string
{
$key = Property::get(self::PROP_API_KEY, null);
if (empty($key))
return null;
return $key;
}
public static function setApiKey(?string $key): void
{
Property::set(self::PROP_API_KEY, empty($key) ? null : $key);
}
}