summaryrefslogblamecommitdiffstats
path: root/modules-available/webinterface/page.inc.php
blob: fb982616df0465d8c6999a9a1a5f15b6b3fb728c (plain) (tree)
1
2
3
4
5
6
7
8
9







                                         
                                          
                                                                

                                                   

                                                                  
                                     

                                                                     

                                        

                                                                        
                                      
                                             

                                                                      
                                      




                                                                         
                                


                                                                                            
                 



                                                           



                                               
                                             

                                              
                              


                                                       






                                                              
                                                                       
                              
                 
                                      

                                                                                                                                      
                 

                                                             
                                                                                          
                 
                                                   
         
 





                                                                                                       


                                                                
                                                                                         






                                                                                         

                                     
                                               


                        
                                                             



                                                                             
                 
                                                               




                                                                                                                


                                                                               



                                                                                  
                                                                        

                                                                                 
                  






                                                                                     
                                                                                                           


                                                                                    
                                                                






                                                                                              
                                                    

                         













                                                                                            




                                                                         
                                                          
                                                 
                                             












                                                                                             
                         
                                                                        
                 
                                                                                                                                                 
                                                                                



                                                    
                                
                                                                  
                                                           
                        
                                                           
                 
                                                                                   
                                                        


                                
                                                                              

                                                                                                                                                











                                                                                               
                                                                                 
                                                            

         










                                                                                      
                                               
         
                                                      

         
                                                      
         






                                                                                              
                                                               



                                               
                                                               




























                                                                                                   





                                                                                                                 

         








                                                                    
 
 
<?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);
		}
	}

}