#!/usr/bin/env php
<?php

/**
 * Import SSL certificates from a pre-determined place on the filesystem.
 * Once imported, set them for use in the GUI
 */

if (empty($argc)) {
	echo "Only accessible from the CLI.\r\n";
	die(1);
}

if ($argc != 3) {
	echo "Usage: php " . $argv[0] . " /path/to/certificate.crt /path/to/private/key.pem \r\n";
	die(1);
}

require_once "certs.inc";
require_once "pfsense-utils.inc";
require_once "functions.inc";
require_once "filter.inc";
require_once "shaper.inc";

$certificate = trim(file_get_contents($argv[1]));
$key = trim(file_get_contents($argv[2]));

// Do some quick verification of the certificate, similar to what the GUI does
if (empty($certificate)) {
	echo "The certificate is empty.\r\n";
	die(1);
}
if (!strstr($certificate, "BEGIN CERTIFICATE") || !strstr($certificate, "END CERTIFICATE")) {
	echo "This certificate does not appear to be valid.\r\n";
	die(1);
}

// Verification that the certificate matches the key
if (empty($key)) {
	echo "The key is empty.\r\n";
	die(1);
}
if (cert_get_publickey($certificate, false) != cert_get_publickey($key, false, 'prv')) {
	echo "The private key does not match the certificate.\r\n";
	die(1);
}

$cert = array();
//$cert['refid'] = uniqid();
$cert['refid'] = hash('sha256', $certificate); // if we don't take a hash each iteration will have a new unique id and update each time
$cert['descr'] = "Certificate added to pfsense through " . $argv[0] . " on " . date("Y/m/d");

echo "Certificate sha256: ".$cert['refid']."\r\n";

cert_import($cert, $certificate, $key);

// Set up the existing certificate store
// Copied from system_certmanager.php
if (!is_array($config['ca'])) {
	$config['ca'] = array();
}

$a_ca =& $config['ca'];

if (!is_array($config['cert'])) {
	$config['cert'] = array();
}

$a_cert =& $config['cert'];

echo "Current Web GUI Certificate ID: `".$config['system']['webgui']['ssl-certref']."`\r\n";
echo "Current Unbound Certificate ID: `".$config['unbound']['sslcertref']."`\r\n";

$updateConfig = true;
// Check if the certificate we just parsed is already imported (we'll check the certificate portion)
foreach ($a_cert as $existing_cert) {
	if ($existing_cert['crt'] === $cert['crt']) {
		$updateConfig = false; // no need to update cert configuration
	}
}

if ($updateConfig) {
	// Append the final certificate
	$a_cert[] = $cert;
	// Write out the updated configuration
	write_config("Save new certificate config, from pfsense-import-certificate.php");
	sleep(3); //sleep to space out the write_config calls so they show distinctly
} else {
	echo "Certificate is already installed, skipping configuration update.\r\n";
}

echo "Checking if services are using the latest certificate.\r\n";

$updateCertWebGui = false;

if ($config['system']['webgui']['ssl-certref'] !== $cert['refid']) {
	echo "Web GUI certificate changed, installing!\r\n";
	$updateCertWebGui = true;
}

// Assuming that all worked, we now need to set the new certificate for use in the GUI
$config['system']['webgui']['ssl-certref'] = $cert['refid'];

$updateCertUnbound = false;

// If Unbound is set to use TLS, then set the new certificate to be used by Unbound also.
if (isset($config['unbound']['enable']) and isset($config['unbound']['enablessl'])) {
	if ($config['unbound']['sslcertref'] !== $cert['refid']) {
		echo "Unbound certificate changed, installing!\r\n";
		$updateCertUnbound = true;
	}
	$config['unbound']['sslcertref'] = $cert['refid'];
} else {
	echo "Unbound SSL is disabled, ignoring.\r\n";
}

$updateCertCaptivePortal = false;

// If captive portal is set to use TLS, then set the new certificate
//   to be used by captive portals
foreach ($config['captiveportal'] as $cpid => $cportal) {
	if(isset($cportal['enable']) and isset($cportal['httpslogin'])){
		if ($config['captiveportal'][$cpid]['certref'] !== $cert['refid']) {
			echo "Captive Portal certificate changed, installing!\r\n";
			$updateCertCaptivePortal = true;
		}
		$config['captiveportal'][$cpid]['certref'] = $cert['refid'];
	}
}

if (!$updateCertWebGui and !$updateCertUnbound and !$updateCertCaptivePortal) {
	echo "No certificate changes, no need to update config.\r\n";
	die(); // exit with a valid error code, as this is intended behaviour
}

write_config("Set new certificate as active for webgui, from pfsense-import-certificate.php");
sleep(3); //sleep to space out the write_config calls

//Use cert_get_all_services to grab all services now using the new cert.

$services = cert_get_all_services($cert['refid']);

//Restart all services that are using the new cert.
echo "Restart services that are using the new certificate.\r\n";
var_dump($services);
log_error(gettext("Restart services that are using the new cert"));

cert_restart_services($services);
//All except unbound, there is a bug in cert_restart_services... it doesn't restart unbound
//https://redmine.pfsense.org/issues/15062

echo "Completed! New certificate installed.\r\n";

// If unbound cert was updated, then restart unbound.
// Remove once bug fixed
if ($updateCertUnbound) {
  echo "Restart Unbound\n";
  log_error(gettext("Unbound configuration has changed. Restarting Unbound."));
  service_control_restart('unbound','');
}


// Delete unused certificates added by this script

$a_cert =& $config['cert'];
$name = '';
foreach ($a_cert as $cid => $acrt) {
	echo "Validating certificate $cid for deletion.\r\n";
	if (!cert_in_use($acrt['refid']) and preg_match("/pfsense-import-certificate\.php/",$acrt['descr'])) {
		echo "--> Delete, no longer in use.\r\n";
		// cert not in use and matches description pattern
		$name.=htmlspecialchars($acrt['descr'])." ";
		unset($a_cert[$cid]);
	} else {
		echo "--> Ignoring.\r\n";
	}
}

if($name){
        echo "Deleted old certificates: save the config.\r\n";
        $savemsg = sprintf(gettext("Deleted certificate: %s , from pfsense-import-certificate.php"), $name);
        write_config($savemsg);
}
