1900 lines
59 KiB
PHP
1900 lines
59 KiB
PHP
|
|
<?php
|
|||
|
|
defined('ABSPATH') or die("you do not have access to this page!");
|
|||
|
|
|
|||
|
|
require_once rsssl_le_path . 'vendor/autoload.php';
|
|||
|
|
require_once rsssl_path . 'lib/admin/class-encryption.php';
|
|||
|
|
use RSSSL\lib\admin\Encryption;
|
|||
|
|
use LE_ACME2\Account;
|
|||
|
|
use LE_ACME2\Authorizer\AbstractDNSWriter;
|
|||
|
|
use LE_ACME2\Authorizer\DNS;
|
|||
|
|
use LE_ACME2\Authorizer\HTTP;
|
|||
|
|
use LE_ACME2\Connector\Connector;
|
|||
|
|
use LE_ACME2\Order;
|
|||
|
|
use LE_ACME2\Utilities\Certificate;
|
|||
|
|
use LE_ACME2\Utilities\Logger;
|
|||
|
|
|
|||
|
|
class rsssl_letsencrypt_handler {
|
|||
|
|
use Encryption;
|
|||
|
|
|
|||
|
|
private static $_this;
|
|||
|
|
/**
|
|||
|
|
* Account object
|
|||
|
|
* @var bool|LE_ACME2\Account
|
|||
|
|
*/
|
|||
|
|
public $account = false;
|
|||
|
|
public $challenge_directory = false;
|
|||
|
|
public $key_directory = false;
|
|||
|
|
public $certs_directory = false;
|
|||
|
|
public $subjects = array();
|
|||
|
|
|
|||
|
|
public function __construct()
|
|||
|
|
{
|
|||
|
|
if ( isset( self::$_this ) ) {
|
|||
|
|
wp_die();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
add_action('admin_init', array($this, 'upgrade'));
|
|||
|
|
|
|||
|
|
//loading of these hooks is stricter. The class can be used in the notices, which are needed on the generic dashboard
|
|||
|
|
//These functionality is not needed on the dashboard, so should only be loaded in strict circumstances
|
|||
|
|
if ( rsssl_letsencrypt_generation_allowed( true ) ) {
|
|||
|
|
add_action( 'rsssl_after_save_field', array( $this, 'after_save_field' ), 10, 4 );
|
|||
|
|
add_action( 'admin_init', array( $this, 'maybe_add_htaccess_exclude'));
|
|||
|
|
add_action( 'admin_init', array( $this, 'maybe_create_htaccess_directories'));
|
|||
|
|
|
|||
|
|
$this->key_directory = $this->key_directory();
|
|||
|
|
$this->challenge_directory = $this->challenge_directory();
|
|||
|
|
$this->certs_directory = $this->certs_directory();
|
|||
|
|
|
|||
|
|
// Config the desired paths
|
|||
|
|
if ( $this->key_directory ) {
|
|||
|
|
Account::setCommonKeyDirectoryPath( $this->key_directory );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( $this->challenge_directory ) {
|
|||
|
|
HTTP::setDirectoryPath( $this->challenge_directory );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// General configs
|
|||
|
|
//$use_staging = defined('RSSSL_LE_DEBUG');
|
|||
|
|
Connector::getInstance()->useStagingServer( false );
|
|||
|
|
Logger::getInstance()->setDesiredLevel( Logger::LEVEL_DISABLED );
|
|||
|
|
|
|||
|
|
if ( !rsssl_get_option( 'disable_ocsp' ) ) {
|
|||
|
|
Certificate::enableFeatureOCSPMustStaple();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Order::setPreferredChain('ISRG Root X1');
|
|||
|
|
$this->subjects = $this->get_subjects();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self::$_this = $this;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static function this() {
|
|||
|
|
return self::$_this;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* If we're on apache, add a line to the .htaccess so the acme challenge directory won't get blocked.
|
|||
|
|
*/
|
|||
|
|
public function maybe_add_htaccess_exclude(){
|
|||
|
|
|
|||
|
|
if (!rsssl_user_can_manage()) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( !RSSSL()->server->uses_htaccess() ) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$htaccess_file = RSSSL()->admin->htaccess_file();
|
|||
|
|
if ( !file_exists($htaccess_file) ) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( !is_writable($htaccess_file) ) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$htaccess = file_get_contents( $htaccess_file );
|
|||
|
|
|
|||
|
|
//if it's already inserted, skip.
|
|||
|
|
if ( strpos($htaccess, 'Really Simple Security LETS ENCRYPT') !== FALSE ) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$htaccess = preg_replace("/#\s?BEGIN\s?Really Simple Security LETS ENCRYPT.*?#\s?END\s?Really Simple Security LETS ENCRYPT/s", "", $htaccess);
|
|||
|
|
$htaccess = preg_replace("/\n+/", "\n", $htaccess);
|
|||
|
|
|
|||
|
|
$rules = '#BEGIN Really Simple Security LETS ENCRYPT'."\n";
|
|||
|
|
$rules .= 'RewriteRule ^.well-known/(.*)$ - [L]'."\n";
|
|||
|
|
$rules .= '#END Really Simple Security LETS ENCRYPT'."\n";
|
|||
|
|
$htaccess = $rules . $htaccess;
|
|||
|
|
file_put_contents($htaccess_file, $htaccess);
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if we have an installation failed state.
|
|||
|
|
* @return bool
|
|||
|
|
*/
|
|||
|
|
public function installation_failed(){
|
|||
|
|
$installation_active = get_option("rsssl_le_start_installation");
|
|||
|
|
$installation_failed = get_option("rsssl_installation_error");
|
|||
|
|
|
|||
|
|
return $installation_active && $installation_failed;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Cleanup. If user did not consent to storage, all password fields should be removed on activation, unless they're needed for renewals
|
|||
|
|
*
|
|||
|
|
* @return bool
|
|||
|
|
*/
|
|||
|
|
public function cleanup_on_ssl_activation(){
|
|||
|
|
if ( !current_user_can('manage_security') ) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
$delete_credentials = !rsssl_get_option('store_credentials');
|
|||
|
|
$le_fields = rsssl_le_add_fields([]);
|
|||
|
|
if ( !$this->certificate_automatic_install_possible() || !$this->certificate_install_required() || $delete_credentials ) {
|
|||
|
|
$le_fields = array_filter($le_fields, function($i){
|
|||
|
|
return isset( $i['type'] ) && $i['type'] === 'password';
|
|||
|
|
});
|
|||
|
|
$options = get_option( 'rsssl_options' );
|
|||
|
|
foreach ($le_fields as $index => $field ) {
|
|||
|
|
unset($options[$field['id']]);
|
|||
|
|
}
|
|||
|
|
update_option( 'rsssl_options', $options, false );
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* some custom actions after a field has been saved
|
|||
|
|
* @param string $fieldname
|
|||
|
|
* @param mixed $fieldvalue
|
|||
|
|
* @param mixed $prev_value
|
|||
|
|
* @param string $type
|
|||
|
|
*
|
|||
|
|
* @return void
|
|||
|
|
*/
|
|||
|
|
public function after_save_field(
|
|||
|
|
$fieldname, $fieldvalue, $prev_value, $type
|
|||
|
|
) {
|
|||
|
|
rsssl_progress_add( 'domain' );
|
|||
|
|
//only run when changes have been made
|
|||
|
|
if ( $fieldvalue === $prev_value ) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$options = [
|
|||
|
|
'directadmin_password',
|
|||
|
|
'cpanel_password',
|
|||
|
|
'cloudways_api_key',
|
|||
|
|
'plesk_password',
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
if ( in_array($fieldname, $options) && strpos( $fieldvalue, 'rsssl_' ) === false ) {
|
|||
|
|
rsssl_update_option($fieldname, $this->encrypt_with_prefix($fieldvalue) );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( $fieldname==='other_host_type' ){
|
|||
|
|
if ( !rsssl_do_local_lets_encrypt_generation() ) {
|
|||
|
|
rsssl_progress_add('directories');
|
|||
|
|
rsssl_progress_add('generation');
|
|||
|
|
rsssl_progress_add('dns-verification');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( $fieldname==='email' ){
|
|||
|
|
if ( !is_email($fieldvalue) ) {
|
|||
|
|
rsssl_progress_remove('domain');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Test for localhost or subfolder usage
|
|||
|
|
* @return RSSSL_RESPONSE
|
|||
|
|
*/
|
|||
|
|
public function check_domain(){
|
|||
|
|
$details = parse_url(site_url());
|
|||
|
|
$path = isset($details['path']) ? $details['path'] : '';
|
|||
|
|
if ( strpos(site_url(), 'localhost')!==false ) {
|
|||
|
|
rsssl_progress_remove( 'system-status' );
|
|||
|
|
$action = 'stop';
|
|||
|
|
$status = 'error';
|
|||
|
|
$message = __( "It is not possible to install Let's Encrypt on a localhost environment.", "really-simple-ssl" );
|
|||
|
|
} else if (is_multisite() && get_current_blog_id() !== get_main_site_id() ) {
|
|||
|
|
rsssl_progress_remove('system-status');
|
|||
|
|
$action = 'stop';
|
|||
|
|
$status = 'error';
|
|||
|
|
$message = __("It is not possible to install Let's Encrypt on a subsite. Please go to the main site of your website.", "really-simple-ssl" );
|
|||
|
|
} else if ( strlen($path)>0 ) {
|
|||
|
|
rsssl_progress_remove('system-status');
|
|||
|
|
$action = 'stop';
|
|||
|
|
$status = 'error';
|
|||
|
|
$message = __("It is not possible to install Let's Encrypt on a subfolder configuration.", "really-simple-ssl" ).rsssl_le_read_more(rsssl_link('install-ssl-on-subfolders') );
|
|||
|
|
} elseif ( rsssl_caa_record_prevents_le() ) {
|
|||
|
|
$action = 'stop';
|
|||
|
|
$status = 'error';
|
|||
|
|
$message = __("Please adjust the CAA records via your DNS provider to allow Let’s Encrypt SSL certificates", "really-simple-ssl" ).rsssl_le_read_more(rsssl_link('instructions/edit-dns-caa-records-to-allow-lets-encrypt-ssl-certificates/') );
|
|||
|
|
} else {
|
|||
|
|
$action = 'continue';
|
|||
|
|
$status = 'success';
|
|||
|
|
$message = __("Your domain meets the requirements for Let's Encrypt.", "really-simple-ssl" );
|
|||
|
|
}
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Get certificate installation URL
|
|||
|
|
* @return RSSSL_RESPONSE
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
public function search_ssl_installation_url(){
|
|||
|
|
//start with most generic, then more specific if possible.
|
|||
|
|
$url = rsssl_link('install-ssl-certificate');
|
|||
|
|
$host = 'enter-your-dashboard-url-here';
|
|||
|
|
|
|||
|
|
if (function_exists('wp_get_direct_update_https_url') && !empty(wp_get_direct_update_https_url())) {
|
|||
|
|
$url = wp_get_direct_update_https_url();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( rsssl_is_cpanel() ) {
|
|||
|
|
$cpanel = new rsssl_cPanel();
|
|||
|
|
$host = $cpanel->host;
|
|||
|
|
$url = $cpanel->ssl_installation_url;
|
|||
|
|
} else if ( rsssl_is_plesk() ) {
|
|||
|
|
$plesk = new rsssl_plesk();
|
|||
|
|
$host = $plesk->host;
|
|||
|
|
$url = $plesk->ssl_installation_url;
|
|||
|
|
} else if ( rsssl_is_directadmin() ) {
|
|||
|
|
$directadmin = new rsssl_directadmin();
|
|||
|
|
$host = $directadmin->host;
|
|||
|
|
$url = $directadmin->ssl_installation_url;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$hosting_company = rsssl_get_other_host();
|
|||
|
|
if ( $hosting_company && $hosting_company !== 'none' ) {
|
|||
|
|
$hosting_specific_link = RSSSL_LE()->hosts->hosts[$hosting_company]['ssl_installation_link'];
|
|||
|
|
if ($hosting_specific_link) {
|
|||
|
|
$site = trailingslashit( str_replace(array('https://','http://', 'www.'),'', site_url()) );
|
|||
|
|
if ( strpos($hosting_specific_link,'{host}') !==false && empty($host) ) {
|
|||
|
|
$url = '';
|
|||
|
|
} else {
|
|||
|
|
$url = str_replace(array('{host}', '{domain}'), array($host, $site), $hosting_specific_link);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$action = 'continue';
|
|||
|
|
$status = 'warning';
|
|||
|
|
$message = rsssl_get_manual_instructions_text($url);
|
|||
|
|
$output = $url;
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message, $output );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Test for localhost usage
|
|||
|
|
* @return RSSSL_RESPONSE
|
|||
|
|
*/
|
|||
|
|
public function certificate_status(){
|
|||
|
|
delete_transient('rsssl_certinfo');
|
|||
|
|
if ( RSSSL()->certificate->is_valid() ) {
|
|||
|
|
//we have now renewed the cert info transient
|
|||
|
|
$certinfo = get_transient('rsssl_certinfo');
|
|||
|
|
$end_date = isset($certinfo['validTo_time_t']) ? $certinfo['validTo_time_t'] : false;
|
|||
|
|
$grace_period = strtotime('+'.rsssl_le_manual_generation_renewal_check.' days');
|
|||
|
|
$expiry_date = !empty($end_date) ? date( get_option('date_format'), $end_date ) : __("(unknown)","really-simple-ssl");
|
|||
|
|
//if the certificate expires within the grace period, allow renewal
|
|||
|
|
//e.g. expiry date 30 may, now = 10 may => grace period 9 june.
|
|||
|
|
if ( $grace_period > $end_date ) {
|
|||
|
|
$action = 'continue';
|
|||
|
|
$status = 'success';
|
|||
|
|
$message = sprintf(__("Your certificate will expire on %s.", "really-simple-ssl" ).' '.__("Continue to renew.", "really-simple-ssl" ), $expiry_date); ;
|
|||
|
|
} else {
|
|||
|
|
$action = 'continue';
|
|||
|
|
$status = 'error';
|
|||
|
|
$message = __("You already have a valid SSL certificate.", "really-simple-ssl" );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} else {
|
|||
|
|
$action = 'continue';
|
|||
|
|
$status = 'success';
|
|||
|
|
$message = __("SSL certificate should be generated and installed.", "really-simple-ssl" );
|
|||
|
|
}
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if the certifiate is to expire in max rsssl_le_manual_generation_renewal_check days.
|
|||
|
|
* Used in notices list
|
|||
|
|
* @return bool
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
public function certificate_about_to_expire() {
|
|||
|
|
$about_to_expire = RSSSL()->certificate->about_to_expire();
|
|||
|
|
if ( !$about_to_expire ) {
|
|||
|
|
//if the certificate is valid, stop any attempt to renew.
|
|||
|
|
delete_option('rsssl_le_start_renewal');
|
|||
|
|
delete_option('rsssl_le_start_installation');
|
|||
|
|
return false;
|
|||
|
|
} else {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Test for server software
|
|||
|
|
* @return RSSSL_RESPONSE
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
public function server_software(){
|
|||
|
|
$action = 'continue';
|
|||
|
|
$status = 'warning';
|
|||
|
|
$message = __("The Hosting Panel software was not recognized. Depending on your hosting provider, the generated certificate may need to be installed manually.", "really-simple-ssl" );
|
|||
|
|
|
|||
|
|
if ( rsssl_is_cpanel() ) {
|
|||
|
|
$status = 'success';
|
|||
|
|
$message = __("CPanel recognized. Possibly the certificate can be installed automatically.", "really-simple-ssl" );
|
|||
|
|
} else if ( rsssl_is_plesk() ) {
|
|||
|
|
$status = 'success';
|
|||
|
|
$message = __("Plesk recognized. Possibly the certificate can be installed automatically.", "really-simple-ssl" );
|
|||
|
|
} else if ( rsssl_is_directadmin() ) {
|
|||
|
|
$status = 'success';
|
|||
|
|
$message = __("DirectAdmin recognized. Possibly the certificate can be installed automatically.", "really-simple-ssl" );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if CURL is available
|
|||
|
|
*
|
|||
|
|
* @return RSSSL_RESPONSE
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
public function curl_exists() {
|
|||
|
|
if( function_exists('curl_init') === false ){
|
|||
|
|
$action = 'stop';
|
|||
|
|
$status = 'error';
|
|||
|
|
$message = __("The PHP function CURL is not available on your server, which is required. Please contact your hosting provider.", "really-simple-ssl" );
|
|||
|
|
} else {
|
|||
|
|
$action = 'continue';
|
|||
|
|
$status = 'success';
|
|||
|
|
$message = __("The PHP function CURL has successfully been detected.", "really-simple-ssl" );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Get or create an account
|
|||
|
|
* @return RSSSL_RESPONSE
|
|||
|
|
*/
|
|||
|
|
public function get_account() {
|
|||
|
|
$account_email = $this->account_email();
|
|||
|
|
if ( is_email($account_email) ) {
|
|||
|
|
try {
|
|||
|
|
$this->account
|
|||
|
|
= ! Account::exists( $account_email ) ?
|
|||
|
|
Account::create( $account_email ) :
|
|||
|
|
Account::get( $account_email );
|
|||
|
|
$status = 'success';
|
|||
|
|
$action = 'continue';
|
|||
|
|
$message = __("Successfully retrieved account", "really-simple-ssl");
|
|||
|
|
} catch(Exception $e) {
|
|||
|
|
$response = $this->get_error($e);
|
|||
|
|
$status = 'error';
|
|||
|
|
$action = 'retry';
|
|||
|
|
if ( strpos($response, 'invalid contact domain')) {
|
|||
|
|
$action = 'stop';
|
|||
|
|
$response = __("The used domain for your email address is not allowed.","really-simple-ssl").' '.
|
|||
|
|
sprintf(__("Please change your email address %shere%s and try again.", "really-simple-ssl"),'<a href="'.rsssl_letsencrypt_wizard_url().'&step=2'.'">','</a>');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$message = $response;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
$status = 'error';
|
|||
|
|
$action = 'stop';
|
|||
|
|
$message = __("The email address was not set. Please set the email address",'really-simple-ssl');
|
|||
|
|
}
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @return RSSSL_RESPONSE
|
|||
|
|
*/
|
|||
|
|
public function get_dns_token()
|
|||
|
|
{
|
|||
|
|
if ( rsssl_is_ready_for('dns-verification') ) {
|
|||
|
|
$use_dns = rsssl_dns_verification_required();
|
|||
|
|
$challenge_type = $use_dns ? Order::CHALLENGE_TYPE_DNS : Order::CHALLENGE_TYPE_HTTP;
|
|||
|
|
if ( $use_dns ) {
|
|||
|
|
try {
|
|||
|
|
$this->get_account();
|
|||
|
|
$dnsWriter = new class extends AbstractDNSWriter {
|
|||
|
|
public function write( Order $order, string $identifier, string $digest ): bool {
|
|||
|
|
$tokens = get_option( 'rsssl_le_dns_tokens', [] );
|
|||
|
|
$tokens[ $identifier ] = $digest;
|
|||
|
|
update_option( "rsssl_le_dns_tokens", $tokens, false );
|
|||
|
|
rsssl_progress_add( 'dns-verification' );
|
|||
|
|
|
|||
|
|
//return false, as we will continue later on.
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
DNS::setWriter( $dnsWriter );
|
|||
|
|
$response = $this->get_order();
|
|||
|
|
$order = $response->output;
|
|||
|
|
$response->output = false;
|
|||
|
|
|
|||
|
|
if ( $order ) {
|
|||
|
|
try {
|
|||
|
|
if ( $order->authorize( $challenge_type ) ) {
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'success',
|
|||
|
|
'continue',
|
|||
|
|
__( "Token successfully retrieved. Click the refresh button if it's not visible yet.", 'really-simple-ssl' ),
|
|||
|
|
$this->get_dns_tokens()
|
|||
|
|
);
|
|||
|
|
} else {
|
|||
|
|
if ( get_option( 'rsssl_le_dns_tokens' ) ) {
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'success',
|
|||
|
|
'continue',
|
|||
|
|
__( "Token successfully retrieved. Click the refresh button if it's not visible yet.", 'really-simple-ssl' ),
|
|||
|
|
$this->get_dns_tokens()
|
|||
|
|
);
|
|||
|
|
} else {
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
'retry',
|
|||
|
|
__( "Token not received yet.", 'really-simple-ssl' )
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
} catch ( Exception $e ) {
|
|||
|
|
$error = $this->get_error( $e );
|
|||
|
|
if ( strpos( $error, 'No challenge found with given type')!==false ) {
|
|||
|
|
//Maybe it was first set to HTTP challenge. retry after clearing the order.
|
|||
|
|
$this->clear_order(true);
|
|||
|
|
} else if (strpos($error, 'Keys exist already')!==false) {
|
|||
|
|
$this->clear_order(true);
|
|||
|
|
$error = __("DNS token not retrieved.", 'really-simple-ssl').' '.__("There are existing keys, the order had to be cleared first.","really-simple-ssl")." ".__("Please start at the previous step.","really-simple-ssl");
|
|||
|
|
} else if (strpos($error, 'Order has status "invalid"')!==false) {
|
|||
|
|
$this->clear_order();
|
|||
|
|
$error = __("DNS token not retrieved.", 'really-simple-ssl').' '.__("The order is invalid, possibly due to too many failed authorization attempts. Please start at the previous step.","really-simple-ssl");
|
|||
|
|
} else
|
|||
|
|
//fixing a plesk bug
|
|||
|
|
if ( strpos($error, 'No order for ID ') !== FALSE){
|
|||
|
|
$error .= ' '.__("Order ID mismatch, regenerate order.","really-simple-ssl");
|
|||
|
|
$this->clear_order();
|
|||
|
|
rsssl_progress_remove('dns-verification');
|
|||
|
|
$error .= ' '.__("If you entered your DNS records before, they need to be changed.","really-simple-ssl");
|
|||
|
|
}
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
'retry',
|
|||
|
|
$error
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if (strpos($response->message, 'Keys exist already')!==false) {
|
|||
|
|
$this->clear_order(true);
|
|||
|
|
$response->message = __("DNS token not retrieved.", 'really-simple-ssl').' '.__("There are existing keys, the order had to be cleared first.","really-simple-ssl")." ".__("Please start at the previous step.","really-simple-ssl");
|
|||
|
|
}
|
|||
|
|
return $response;
|
|||
|
|
}
|
|||
|
|
} catch ( Exception $e ) {
|
|||
|
|
rsssl_progress_remove( 'dns-verification' );
|
|||
|
|
$response = $this->get_error( $e );
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
'retry',
|
|||
|
|
$response
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
'stop',
|
|||
|
|
__( "Configured for HTTP challenge", 'really-simple-ssl' )
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
rsssl_progress_remove( 'dns-verification' );
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
'stop',
|
|||
|
|
$this->not_completed_steps_message('dns-verification')
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
return $response;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Get the DNS tokens
|
|||
|
|
*/
|
|||
|
|
public function get_dns_tokens(): array
|
|||
|
|
{
|
|||
|
|
$tokens = get_option( 'rsssl_le_dns_tokens', [] );
|
|||
|
|
$output = [];
|
|||
|
|
foreach ($tokens as $domain => $token ) {
|
|||
|
|
$output[] = [
|
|||
|
|
'domain' => $domain,
|
|||
|
|
'token' => $token,
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
return $output;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check DNS txt records.
|
|||
|
|
*/
|
|||
|
|
public function verify_dns(): RSSSL_RESPONSE
|
|||
|
|
{
|
|||
|
|
if ( rsssl_is_ready_for('generation') ) {
|
|||
|
|
update_option('rsssl_le_dns_records_verified', false, false );
|
|||
|
|
|
|||
|
|
$tokens = get_option('rsssl_le_dns_tokens');
|
|||
|
|
if ( !$tokens) {
|
|||
|
|
$status = 'error';
|
|||
|
|
$action = 'stop';
|
|||
|
|
$message = __('Token not generated. Please complete the previous step.',"really-simple-ssl");
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
foreach ($tokens as $identifier => $token){
|
|||
|
|
if (strpos($identifier, '*') !== false) continue;
|
|||
|
|
set_error_handler(array($this, 'custom_error_handling'));
|
|||
|
|
ini_set('dns_cache_expiry', 0);
|
|||
|
|
$response = dns_get_record( "_acme-challenge.$identifier", DNS_TXT );
|
|||
|
|
restore_error_handler();
|
|||
|
|
if ( isset($response[0]['txt']) ){
|
|||
|
|
if ($response[0]['txt'] === $token) {
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'success',
|
|||
|
|
'continue',
|
|||
|
|
sprintf(__('Successfully verified DNS records', "really-simple-ssl"), "_acme-challenge.$identifier")
|
|||
|
|
);
|
|||
|
|
update_option('rsssl_le_dns_records_verified', true, false );
|
|||
|
|
} else {
|
|||
|
|
$ttl = $response[0]['ttl'] ?? 0;
|
|||
|
|
$ttl = $this->format_duration($ttl);
|
|||
|
|
$action = get_option('rsssl_skip_dns_check') ? 'continue' : 'stop';
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
$action,
|
|||
|
|
sprintf(__('The DNS response for %s was %s, while it should be %s.', "really-simple-ssl"), "_acme-challenge.$identifier", $response[0]['txt'], $token ). ' '.
|
|||
|
|
sprintf(__("Please wait %s before trying again, as this is the expiration of the DNS record currently.", 'really-simple-ssl'), $ttl)
|
|||
|
|
);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
$action = get_option('rsssl_skip_dns_check') ? 'continue' : 'stop';
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'warning',
|
|||
|
|
$action,
|
|||
|
|
sprintf(__('Could not verify TXT record for domain %s', "really-simple-ssl"), "_acme-challenge.$identifier")
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} else {
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
'stop',
|
|||
|
|
$this->not_completed_steps_message('dns-verification')
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $response;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private function format_duration($seconds)
|
|||
|
|
{
|
|||
|
|
$seconds = (int) $seconds;
|
|||
|
|
if ($seconds >= 3600) {
|
|||
|
|
$hours = floor($seconds / 3600);
|
|||
|
|
$minutes = floor(($seconds % 3600) / 60);
|
|||
|
|
$secs = $seconds % 3600 % 60;
|
|||
|
|
return sprintf(__("%d:%02d:%02d hours", 'really-simple-ssl'), $hours, $minutes, $secs);
|
|||
|
|
} elseif ($seconds >= 60) {
|
|||
|
|
$minutes = floor($seconds / 60);
|
|||
|
|
$secs = $seconds % 60;
|
|||
|
|
return sprintf(__("%d:%02d minutes", 'really-simple-ssl'), $minutes, $secs);
|
|||
|
|
} else {
|
|||
|
|
return sprintf(__("%d seconds", 'really-simple-ssl'), $seconds);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Clear an existing order
|
|||
|
|
*/
|
|||
|
|
public function clear_order( $clear_keys = false )
|
|||
|
|
{
|
|||
|
|
if ( $clear_keys ) {
|
|||
|
|
$this->clear_keys_directory();
|
|||
|
|
}
|
|||
|
|
$this->get_account();
|
|||
|
|
if ( $this->account ) {
|
|||
|
|
$response = $this->get_order();
|
|||
|
|
$order = $response->output;
|
|||
|
|
if ( $order ) {
|
|||
|
|
$order->clear();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Authorize the order
|
|||
|
|
*/
|
|||
|
|
public function create_bundle_or_renew(): RSSSL_RESPONSE
|
|||
|
|
{
|
|||
|
|
$bundle_completed = false;
|
|||
|
|
$use_dns = rsssl_dns_verification_required();
|
|||
|
|
$attempt_count = (int) get_transient( 'rsssl_le_generate_attempt_count' );
|
|||
|
|
if ( $attempt_count>10 ){
|
|||
|
|
delete_option("rsssl_le_start_renewal");
|
|||
|
|
$message = __("The certificate generation was rate limited for 5 minutes because the authorization failed.",'really-simple-ssl');
|
|||
|
|
if ($use_dns){
|
|||
|
|
$message .= ' '.__("Please double check your DNS txt record.",'really-simple-ssl');
|
|||
|
|
}
|
|||
|
|
return new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
'stop',
|
|||
|
|
$message
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( !get_option('rsssl_skip_dns_check') ) {
|
|||
|
|
if ( $use_dns && ! get_option( 'rsssl_le_dns_records_verified' ) ) {
|
|||
|
|
return new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
'stop',
|
|||
|
|
__( "DNS records were not verified yet. Please complete the previous step.", 'really-simple-ssl' )
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( rsssl_is_ready_for('generation') ) {
|
|||
|
|
|
|||
|
|
$this->get_account();
|
|||
|
|
if ( $use_dns ) {
|
|||
|
|
if ( defined('WP_DEBUG') && WP_DEBUG ) {
|
|||
|
|
error_log( "DNS verified: " . get_option( 'rsssl_le_dns_records_verified' ) );
|
|||
|
|
error_log( "Skip DNS check: " . get_option( 'rsssl_skip_dns_check' ) );
|
|||
|
|
}
|
|||
|
|
$dnsWriter = new class extends AbstractDNSWriter {
|
|||
|
|
public function write( Order $order, string $identifier, string $digest): bool {
|
|||
|
|
$status = false;
|
|||
|
|
if ( get_option('rsssl_le_dns_tokens') ) {
|
|||
|
|
$status = true;
|
|||
|
|
}
|
|||
|
|
return $status;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
DNS::setWriter($dnsWriter);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$response = $this->get_order();
|
|||
|
|
$order = $response->output;
|
|||
|
|
$response->output = false;
|
|||
|
|
|
|||
|
|
if ( $order ) {
|
|||
|
|
if ( $order->isCertificateBundleAvailable() ) {
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$order->enableAutoRenewal();
|
|||
|
|
$bundle_completed = $this->update_certificate_paths($order);
|
|||
|
|
if ( $bundle_completed ) {
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'success',
|
|||
|
|
'continue',
|
|||
|
|
__("Certificate already generated. It was renewed if required.",'really-simple-ssl')
|
|||
|
|
);
|
|||
|
|
} else {
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
'retry',
|
|||
|
|
__("Files not created yet...",'really-simple-ssl')
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
} catch ( Exception $e ) {
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
'retry',
|
|||
|
|
$this->get_error( $e )
|
|||
|
|
);
|
|||
|
|
$bundle_completed = false;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
$finalized = false;
|
|||
|
|
$challenge_type = $use_dns ? Order::CHALLENGE_TYPE_DNS : Order::CHALLENGE_TYPE_HTTP;
|
|||
|
|
try {
|
|||
|
|
if ( $order->authorize( $challenge_type ) ) {
|
|||
|
|
$order->finalize();
|
|||
|
|
$this->reset_attempt();
|
|||
|
|
$finalized = true;
|
|||
|
|
} else {
|
|||
|
|
$this->count_attempt();
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
'retry',
|
|||
|
|
__('Authorization not completed yet.',"really-simple-ssl")
|
|||
|
|
);
|
|||
|
|
$bundle_completed = false;
|
|||
|
|
}
|
|||
|
|
} catch ( Exception $e ) {
|
|||
|
|
$this->count_attempt();
|
|||
|
|
$message = $this->get_error( $e );
|
|||
|
|
if ( defined('WP_DEBUG') && WP_DEBUG ) {
|
|||
|
|
error_log("Really Simple Security: ".$message);
|
|||
|
|
}
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
'stop',
|
|||
|
|
$message
|
|||
|
|
);
|
|||
|
|
if ( strpos( $message, 'No challenge found with given type')!==false ) {
|
|||
|
|
//Maybe it was first set to HTTP challenge. retry after clearing the order.
|
|||
|
|
$response->message = __("Due to a change in challenge type, the order had to be reset. Please start at the previous step.","really-simple-ssl");
|
|||
|
|
$this->clear_order(true);
|
|||
|
|
} else if ( strpos($message, 'Order has status "invalid"')!==false) {
|
|||
|
|
$this->clear_order();
|
|||
|
|
$response->message = __("Certificate not created.", 'really-simple-ssl').' '.__("The order is invalid, possibly due to too many failed authorization attempts. Please start at the previous step.","really-simple-ssl");
|
|||
|
|
if ($use_dns) {
|
|||
|
|
rsssl_progress_remove('dns-verification');
|
|||
|
|
$response->message .= ' '.__("As your order will be regenerated, you'll need to update your DNS text records.","really-simple-ssl");
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
//if OCSP is not disabled yet, and the order status is not invalid, we disable ocsp, and try again.
|
|||
|
|
if ( !rsssl_get_option( 'disable_ocsp' ) ) {
|
|||
|
|
rsssl_update_option( 'disable_ocsp', true );
|
|||
|
|
$response->action = 'retry';
|
|||
|
|
$response->status = 'warning';
|
|||
|
|
$response->message = __("OCSP not supported, the certificate will be generated without OCSP.","really-simple-ssl");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( $finalized ) {
|
|||
|
|
try {
|
|||
|
|
if ( $order->isCertificateBundleAvailable() ) {
|
|||
|
|
$bundle_completed = $this->update_certificate_paths($order);
|
|||
|
|
|
|||
|
|
if ( $bundle_completed ) {
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'success',
|
|||
|
|
'continue',
|
|||
|
|
__("Successfully generated certificate.",'really-simple-ssl')
|
|||
|
|
);
|
|||
|
|
} else {
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
'retry',
|
|||
|
|
__("Files not created yet...",'really-simple-ssl')
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} else {
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
'retry',
|
|||
|
|
__("Bundle not available yet...",'really-simple-ssl')
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
} catch ( Exception $e ) {
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
'retry',
|
|||
|
|
$this->get_error( $e )
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
'stop',
|
|||
|
|
$this->not_completed_steps_message('generation')
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( $bundle_completed ){
|
|||
|
|
rsssl_progress_add('generation');
|
|||
|
|
update_option('rsssl_le_certificate_generated_by_rsssl', true, false);
|
|||
|
|
delete_option("rsssl_le_start_renewal");
|
|||
|
|
} else {
|
|||
|
|
rsssl_progress_remove('generation');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $response;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* For each file, check if the path exists, update the path options, and
|
|||
|
|
* return success or false accordingly
|
|||
|
|
*/
|
|||
|
|
private function update_certificate_paths($order): bool
|
|||
|
|
{
|
|||
|
|
$bundle = $order->getCertificateBundle();
|
|||
|
|
$pathToPrivateKey = $bundle->path . $bundle->private;
|
|||
|
|
$pathToCertificate = $bundle->path . $bundle->certificate;
|
|||
|
|
$pathToIntermediate = $bundle->path . $bundle->intermediate;
|
|||
|
|
$success_private = $success_cert = $success_intermediate = false;
|
|||
|
|
|
|||
|
|
if ( file_exists( $pathToPrivateKey ) ) {
|
|||
|
|
$success_private = true;
|
|||
|
|
update_option( 'rsssl_private_key_path', $pathToPrivateKey, false );
|
|||
|
|
}
|
|||
|
|
if ( file_exists( $pathToCertificate ) ) {
|
|||
|
|
$success_cert = true;
|
|||
|
|
update_option( 'rsssl_certificate_path', $pathToCertificate, false );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( file_exists( $pathToIntermediate ) ) {
|
|||
|
|
$success_intermediate = true;
|
|||
|
|
update_option( 'rsssl_intermediate_path', $pathToIntermediate, false );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$bundle_completed = true;
|
|||
|
|
if ( ! $success_cert || ! $success_private || ! $success_intermediate ) {
|
|||
|
|
$bundle_completed = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $bundle_completed;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Get the order object
|
|||
|
|
*/
|
|||
|
|
public function get_order(): RSSSL_RESPONSE
|
|||
|
|
{
|
|||
|
|
// if we don't have an account, try to retrieve it
|
|||
|
|
if ( !$this->account ) {
|
|||
|
|
$this->get_account();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// still no account, then exit
|
|||
|
|
if ( !$this->account ) {
|
|||
|
|
return new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
'retry',
|
|||
|
|
__( "Failed retrieving account.", 'really-simple-ssl' )
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( ! Order::exists( $this->account, $this->subjects ) ) {
|
|||
|
|
try {
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'success',
|
|||
|
|
'continue',
|
|||
|
|
__("Order successfully created.",'really-simple-ssl')
|
|||
|
|
);
|
|||
|
|
$response->output = Order::create( $this->account, $this->subjects );
|
|||
|
|
|
|||
|
|
} catch(Exception $e) {
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'error',
|
|||
|
|
'retry',
|
|||
|
|
$this->get_error($e)
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
//order exists already
|
|||
|
|
$response = new RSSSL_RESPONSE(
|
|||
|
|
'success',
|
|||
|
|
'continue',
|
|||
|
|
__( "Order successfully retrieved.", 'really-simple-ssl' )
|
|||
|
|
);
|
|||
|
|
$response->output = Order::get( $this->account, $this->subjects );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $response;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Keep track of certain request counts, to prevent rate limiting by LE
|
|||
|
|
*/
|
|||
|
|
public function count_attempt()
|
|||
|
|
{
|
|||
|
|
$attempt_count = (int) get_transient( 'rsssl_le_generate_attempt_count' );
|
|||
|
|
$attempt_count++;
|
|||
|
|
set_transient('rsssl_le_generate_attempt_count', $attempt_count, 5 * MINUTE_IN_SECONDS);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function reset_attempt(){
|
|||
|
|
delete_transient('rsssl_le_generate_attempt_count');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if SSL generation renewal can be handled automatically
|
|||
|
|
*/
|
|||
|
|
public function ssl_generation_can_auto_renew(): bool
|
|||
|
|
{
|
|||
|
|
if ( rsssl_get_option('verification_type')==='dns' && !get_option('rsssl_le_dns_configured_by_rsssl') ) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if it's possible to autorenew
|
|||
|
|
*/
|
|||
|
|
public function certificate_automatic_install_possible(): bool
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
$install_method = get_option('rsssl_le_certificate_installed_by_rsssl');
|
|||
|
|
|
|||
|
|
//if it was never auto installed, we probably can't autorenew.
|
|||
|
|
if ($install_method === false ) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if the manual renewal should start.
|
|||
|
|
*/
|
|||
|
|
public function should_start_manual_installation_renewal(): bool
|
|||
|
|
{
|
|||
|
|
if ( !$this->should_start_manual_ssl_generation() && get_option( "rsssl_le_start_installation" ) ) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function should_start_manual_ssl_generation()
|
|||
|
|
{
|
|||
|
|
return get_option( "rsssl_le_start_renewal" );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Only used if
|
|||
|
|
* - SSL generated by RSSSL
|
|||
|
|
* - certificate is about to expire
|
|||
|
|
*/
|
|||
|
|
public function certificate_renewal_status_notice(): string
|
|||
|
|
{
|
|||
|
|
if ( !RSSSL_LE()->letsencrypt_handler->ssl_generation_can_auto_renew()){
|
|||
|
|
return 'manual-generation';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( $this->certificate_install_required() &&
|
|||
|
|
$this->certificate_automatic_install_possible() &&
|
|||
|
|
$this->installation_failed()
|
|||
|
|
){
|
|||
|
|
return 'automatic-installation-failed';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( $this->certificate_install_required() && !$this->certificate_automatic_install_possible() ) {
|
|||
|
|
return 'manual-installation';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return 'automatic';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if the certificate has to be installed on each renewal defaults to
|
|||
|
|
* true.
|
|||
|
|
*/
|
|||
|
|
public function certificate_install_required(): bool
|
|||
|
|
{
|
|||
|
|
$install_method = get_option('rsssl_le_certificate_installed_by_rsssl');
|
|||
|
|
$hosting_company = rsssl_get_other_host();
|
|||
|
|
if ( in_array($install_method, RSSSL_LE()->hosts->no_installation_renewal_needed) || in_array($hosting_company, RSSSL_LE()->hosts->no_installation_renewal_needed)) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if the certificate needs renewal.
|
|||
|
|
*/
|
|||
|
|
public function cron_certificate_needs_renewal(): bool
|
|||
|
|
{
|
|||
|
|
$cert_file = get_option('rsssl_certificate_path');
|
|||
|
|
if ( empty($cert_file) ) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$certificate = file_get_contents($cert_file);
|
|||
|
|
$certificateInfo = openssl_x509_parse($certificate);
|
|||
|
|
$valid_to = $certificateInfo['validTo_time_t'];
|
|||
|
|
$in_expiry_days = strtotime( "+".rsssl_le_cron_generation_renewal_check." days" );
|
|||
|
|
if ( $in_expiry_days > $valid_to ) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Get account email
|
|||
|
|
* @return string
|
|||
|
|
*/
|
|||
|
|
public function account_email()
|
|||
|
|
{
|
|||
|
|
//don't use the default value: we want users to explicitly enter a value
|
|||
|
|
return rsssl_get_option('email_address');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Get terms accepted
|
|||
|
|
*/
|
|||
|
|
public function terms_accepted(): RSSSL_RESPONSE
|
|||
|
|
{
|
|||
|
|
//don't use the default value: we want users to explicitly enter a value
|
|||
|
|
$accepted = rsssl_get_option('accept_le_terms');
|
|||
|
|
if ( $accepted ) {
|
|||
|
|
$status = 'success';
|
|||
|
|
$action = 'continue';
|
|||
|
|
$message = __("Terms & Conditions are accepted.",'really-simple-ssl');
|
|||
|
|
} else {
|
|||
|
|
$status = 'error';
|
|||
|
|
$action = 'stop';
|
|||
|
|
$message = __("The Terms & Conditions were not accepted. Please accept in the general settings.",'really-simple-ssl');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Change the email address in an account
|
|||
|
|
* @param $new_email
|
|||
|
|
*/
|
|||
|
|
public function update_account( $new_email )
|
|||
|
|
{
|
|||
|
|
if (!$this->account) return;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$this->account->update($new_email);
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Get list of common names on the certificate
|
|||
|
|
*/
|
|||
|
|
public function get_subjects(): array
|
|||
|
|
{
|
|||
|
|
$subjects = array();
|
|||
|
|
$domain = rsssl_get_domain();
|
|||
|
|
$root = str_replace( 'www.', '', $domain );;
|
|||
|
|
$subjects[] = $domain;
|
|||
|
|
//don't offer aliasses for subdomains
|
|||
|
|
if ( !rsssl_is_subdomain() ) {
|
|||
|
|
if (rsssl_get_option( 'include_alias' )) {
|
|||
|
|
//main is www.
|
|||
|
|
if ( strpos( $domain, 'www.' ) !== false ) {
|
|||
|
|
$alias_domain = $root;
|
|||
|
|
} else {
|
|||
|
|
$alias_domain = 'www.'.$root;
|
|||
|
|
}
|
|||
|
|
$subjects[] = $alias_domain;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( rsssl_wildcard_certificate_required() ) {
|
|||
|
|
$domain = rsssl_get_domain();
|
|||
|
|
//in theory, the main site of a subdomain setup can be a www. domain. But we have to request a certificate without the www.
|
|||
|
|
$domain = str_replace( 'www.', '', $domain );
|
|||
|
|
$subjects = array(
|
|||
|
|
$domain,
|
|||
|
|
'*.' . $domain,
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return apply_filters('rsssl_le_subjects', $subjects);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if we're ready for the next step.
|
|||
|
|
* @param string $item
|
|||
|
|
*
|
|||
|
|
* @return array | bool
|
|||
|
|
*/
|
|||
|
|
public function is_ready_for($item)
|
|||
|
|
{
|
|||
|
|
if ( !rsssl_do_local_lets_encrypt_generation() ) {
|
|||
|
|
rsssl_progress_add('directories');
|
|||
|
|
rsssl_progress_add('generation');
|
|||
|
|
rsssl_progress_add('dns-verification');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( !rsssl_dns_verification_required() ) {
|
|||
|
|
rsssl_progress_add('dns-verification');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (empty(rsssl_get_not_completed_steps($item))){
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Catch errors
|
|||
|
|
*
|
|||
|
|
* @since 3.0
|
|||
|
|
*
|
|||
|
|
* @access public
|
|||
|
|
* @param $errno
|
|||
|
|
* @param $errstr
|
|||
|
|
* @param $errfile
|
|||
|
|
* @param $errline
|
|||
|
|
* @param array $errcontext
|
|||
|
|
*
|
|||
|
|
* @return bool
|
|||
|
|
*/
|
|||
|
|
public function custom_error_handling( $errno, $errstr, $errfile, $errline, $errcontext = array() ) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function not_completed_steps_message($step)
|
|||
|
|
{
|
|||
|
|
$not_completed_steps = rsssl_get_not_completed_steps($step);
|
|||
|
|
$nice_names = array();
|
|||
|
|
$steps = rsssl_le_steps();
|
|||
|
|
foreach ($not_completed_steps as $not_completed_step ) {
|
|||
|
|
$index = array_search($not_completed_step, array_column( $steps, 'id'));
|
|||
|
|
$nice_names[] = $steps[$index]['title'];
|
|||
|
|
}
|
|||
|
|
return sprintf(__('Please complete the following step(s) first: %s', "really-simple-ssl"), implode(", ", $nice_names) );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Test for writing permissions
|
|||
|
|
* @return RSSSL_RESPONSE
|
|||
|
|
*/
|
|||
|
|
public function check_writing_permissions(): RSSSL_RESPONSE
|
|||
|
|
{
|
|||
|
|
$directories_without_permissions = $this->directories_without_writing_permissions();
|
|||
|
|
$has_missing_permissions = count($directories_without_permissions)>0;
|
|||
|
|
|
|||
|
|
if ( $has_missing_permissions ) {
|
|||
|
|
rsssl_progress_remove('directories');
|
|||
|
|
$action = 'stop';
|
|||
|
|
$status = 'error';
|
|||
|
|
$message = __("The following directories do not have the necessary writing permissions.", "really-simple-ssl" )." ".__("Set permissions to 644 to enable SSL generation.", "really-simple-ssl" );
|
|||
|
|
foreach ($directories_without_permissions as $directories_without_permission) {
|
|||
|
|
$message .= "<br> - ".$directories_without_permission;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
$action = 'continue';
|
|||
|
|
$status = 'success';
|
|||
|
|
$message = __("The required directories have the necessary writing permissions.", "really-simple-ssl" );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Verify if a host has been selected, and if so, if this host supports LE, or if it's already active
|
|||
|
|
*/
|
|||
|
|
public function check_host(): RSSSL_RESPONSE
|
|||
|
|
{
|
|||
|
|
$action = 'continue';
|
|||
|
|
$status = 'success';
|
|||
|
|
$message = __("We have not detected any known hosting limitations.", "really-simple-ssl" );
|
|||
|
|
$host = rsssl_get_other_host();
|
|||
|
|
if ( $host === 'none' ) $host = false;
|
|||
|
|
if ( isset(RSSSL_LE()->hosts->hosts[$host]) ){
|
|||
|
|
if ( RSSSL_LE()->hosts->hosts[$host]['free_ssl_available'] === 'paid_only' ) {
|
|||
|
|
$action = 'stop';
|
|||
|
|
$status = 'error';
|
|||
|
|
$message = sprintf(__("According to our information, your hosting provider does not allow any kind of SSL installation, other then their own paid certificate. For an alternative hosting provider with SSL, see this %sarticle%s.","really-simple-ssl"), '<a target="_blank" href="https://really-simple-ssl.com/hosting-providers-with-free-ssl">', '</a>');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( RSSSL_LE()->hosts->hosts[$host]['free_ssl_available'] === 'activated_by_default' ) {
|
|||
|
|
$url = RSSSL_LE()->hosts->hosts[$host]['ssl_installation_link'];
|
|||
|
|
$action = 'continue';
|
|||
|
|
$status = 'error';
|
|||
|
|
$message = sprintf(__("According to our information, your hosting provider supplies your account with an SSL certificate by default. Please contact your %shosting support%s if this is not the case.","really-simple-ssl"), '<a target="_blank" href="'.$url.'">', '</a>').' '.
|
|||
|
|
__("After completing the installation, you can let Really Simple Security automatically configure your site for SSL by using the 'Activate SSL' button.","really-simple-ssl");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Test for directory
|
|||
|
|
*/
|
|||
|
|
public function check_challenge_directory(): RSSSL_RESPONSE
|
|||
|
|
{
|
|||
|
|
if ( !$this->challenge_directory() ) {
|
|||
|
|
rsssl_progress_remove('directories');
|
|||
|
|
$action = 'stop';
|
|||
|
|
$status = 'error';
|
|||
|
|
$message = __("The challenge directory is not created yet.", "really-simple-ssl" );
|
|||
|
|
} else {
|
|||
|
|
$action = 'continue';
|
|||
|
|
$status = 'success';
|
|||
|
|
$message = __("The challenge directory was successfully created.", "really-simple-ssl" );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Test for directory
|
|||
|
|
*/
|
|||
|
|
public function check_key_directory(): RSSSL_RESPONSE
|
|||
|
|
{
|
|||
|
|
$action = 'stop';
|
|||
|
|
$status = 'error';
|
|||
|
|
$message = __("The key directory is not created yet.", "really-simple-ssl" );
|
|||
|
|
//this option is set in the key_dir function, so we need to check it now.
|
|||
|
|
if ( !get_option('rsssl_create_folders_in_root')) {
|
|||
|
|
$action = 'retry';
|
|||
|
|
$message = __("Trying to create directory in root of website.", "really-simple-ssl" );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( !$this->key_directory() ) {
|
|||
|
|
rsssl_progress_remove('directories');
|
|||
|
|
} else {
|
|||
|
|
$action = 'continue';
|
|||
|
|
$status = 'success';
|
|||
|
|
$message = __("The key directory was successfully created.", "really-simple-ssl" );
|
|||
|
|
}
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Test for directory
|
|||
|
|
*/
|
|||
|
|
public function check_certs_directory(): RSSSL_RESPONSE
|
|||
|
|
{
|
|||
|
|
if ( !$this->certs_directory() ) {
|
|||
|
|
rsssl_progress_remove('directories');
|
|||
|
|
$action = 'stop';
|
|||
|
|
$status = 'error';
|
|||
|
|
$message = __("The certs directory is not created yet.", "really-simple-ssl" );
|
|||
|
|
} else {
|
|||
|
|
$action = 'continue';
|
|||
|
|
$status = 'success';
|
|||
|
|
$message = __("The certs directory was successfully created.", "really-simple-ssl" );
|
|||
|
|
}
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if our created directories have the necessary writing permissions
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
public function directories_without_writing_permissions( ){
|
|||
|
|
$required_folders = array(
|
|||
|
|
$this->key_directory,
|
|||
|
|
$this->certs_directory,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if ( !rsssl_dns_verification_required() ) {
|
|||
|
|
$required_folders[] = $this->challenge_directory;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$no_writing_permissions = array();
|
|||
|
|
foreach ($required_folders as $required_folder){
|
|||
|
|
if (!$this->directory_has_writing_permissions( $required_folder )) {
|
|||
|
|
$no_writing_permissions[] = $required_folder;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $no_writing_permissions;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if a directory has writing permissions
|
|||
|
|
* @param string $directory
|
|||
|
|
*
|
|||
|
|
* @return bool
|
|||
|
|
*/
|
|||
|
|
public function directory_has_writing_permissions( $directory ){
|
|||
|
|
set_error_handler(array($this, 'custom_error_handling'));
|
|||
|
|
$test_file = fopen( $directory . "/really-simple-ssl-permissions-check.txt", "w" );
|
|||
|
|
if ( !$test_file ) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fwrite($test_file, 'file to test writing permissions for Really Simple Security');
|
|||
|
|
fclose( $test_file );
|
|||
|
|
restore_error_handler();
|
|||
|
|
if (!file_exists($directory . "/really-simple-ssl-permissions-check.txt")) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if the challenge directory is reachable over the http protocol
|
|||
|
|
* @return RSSSL_RESPONSE
|
|||
|
|
*/
|
|||
|
|
public function challenge_directory_reachable()
|
|||
|
|
{
|
|||
|
|
$file_content = false;
|
|||
|
|
$status_code = __('no response','really-simple-ssl');
|
|||
|
|
//make sure we request over http, otherwise the request might fail if the url is already https.
|
|||
|
|
$url = str_replace('https://', 'http://', site_url('.well-known/acme-challenge/really-simple-ssl-permissions-check.txt'));
|
|||
|
|
|
|||
|
|
$error_message = sprintf(__( "Could not reach challenge directory over %s.", "really-simple-ssl"), '<a target="_blank" href="'.$url.'">'.$url.'</a>');
|
|||
|
|
$test_string = 'Really Simple Security';
|
|||
|
|
$folders = $this->directories_without_writing_permissions();
|
|||
|
|
if ( !$this->challenge_directory() || count($folders) !==0 ) {
|
|||
|
|
$status = 'error';
|
|||
|
|
$action = 'stop';
|
|||
|
|
$message = __( "Challenge directory not writable.", "really-simple-ssl");
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$response = wp_remote_get( $url );
|
|||
|
|
if ( is_array( $response ) ) {
|
|||
|
|
$status_code = wp_remote_retrieve_response_code( $response );
|
|||
|
|
$file_content = wp_remote_retrieve_body( $response );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( $status_code !== 200 ) {
|
|||
|
|
if (get_option('rsssl_skip_challenge_directory_request')) {
|
|||
|
|
$status = 'warning';
|
|||
|
|
$action = 'continue';
|
|||
|
|
$message = $error_message.' '.sprintf( __( "Error code %s", "really-simple-ssl" ), $status_code );
|
|||
|
|
} else {
|
|||
|
|
$status = 'error';
|
|||
|
|
$action = 'stop';
|
|||
|
|
$message = $error_message.' '.sprintf( __( "Error code %s", "really-simple-ssl" ), $status_code );
|
|||
|
|
rsssl_progress_remove('directories');
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if ( ! is_wp_error( $response ) && ( strpos( $file_content, $test_string ) !== false ) ) {
|
|||
|
|
$status = 'success';
|
|||
|
|
$action = 'continue';
|
|||
|
|
$message = __( "Successfully verified alias domain.", "really-simple-ssl" );
|
|||
|
|
set_transient('rsssl_alias_domain_available', 'available', 30 * MINUTE_IN_SECONDS );
|
|||
|
|
} else {
|
|||
|
|
$status = 'error';
|
|||
|
|
$action = 'stop';
|
|||
|
|
$message = $error_message;
|
|||
|
|
rsssl_progress_remove('directories');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if exists, create .well-known/acme-challenge directory if not
|
|||
|
|
* existing
|
|||
|
|
* @return bool|string
|
|||
|
|
*/
|
|||
|
|
public function challenge_directory()
|
|||
|
|
{
|
|||
|
|
$root_directory = trailingslashit(ABSPATH);
|
|||
|
|
if ( ! file_exists( $root_directory . '.well-known' ) ) {
|
|||
|
|
mkdir( $root_directory . '.well-known', 0755 );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( ! file_exists( $root_directory . '.well-known/acme-challenge' ) ) {
|
|||
|
|
mkdir( $root_directory . '.well-known/acme-challenge', 0755 );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( file_exists( $root_directory . '.well-known/acme-challenge' ) ){
|
|||
|
|
return $root_directory . '.well-known/acme-challenge';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Get path to location where to create the directories.
|
|||
|
|
* @uses apply_filters 'rsssl_le_directory_path'
|
|||
|
|
*/
|
|||
|
|
public function get_directory_path(): string
|
|||
|
|
{
|
|||
|
|
$root_directory = trailingslashit(ABSPATH);
|
|||
|
|
$directoryPath = trailingslashit(dirname($root_directory));
|
|||
|
|
|
|||
|
|
if ( get_option('rsssl_create_folders_in_root') ) {
|
|||
|
|
if ( !get_option('rsssl_ssl_dirname') ) {
|
|||
|
|
$token = str_shuffle ( time() );
|
|||
|
|
update_option('rsssl_ssl_dirname', $token, false );
|
|||
|
|
}
|
|||
|
|
if ( ! file_exists( $root_directory . get_option('rsssl_ssl_dirname') ) ) {
|
|||
|
|
mkdir( $root_directory . get_option('rsssl_ssl_dirname'), 0755 );
|
|||
|
|
}
|
|||
|
|
$directoryPath = ($root_directory . trailingslashit( get_option('rsssl_ssl_dirname') ));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Filter: 'rsssl_le_directory_path'
|
|||
|
|
* Can be used to change the directory path where the ssl/keys directory
|
|||
|
|
* is created.
|
|||
|
|
*
|
|||
|
|
* @param string $directoryPath
|
|||
|
|
* @return string
|
|||
|
|
*/
|
|||
|
|
return apply_filters('rsssl_le_directory_path', $directoryPath);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Get path to ssl directory. Note that here is NO trailing slash.
|
|||
|
|
* @uses apply_filters 'rsssl_le_ssl_path'
|
|||
|
|
*/
|
|||
|
|
public function get_ssl_path(): string
|
|||
|
|
{
|
|||
|
|
$directoryPath = $this->get_directory_path();
|
|||
|
|
$sslPath = ($directoryPath . 'ssl');
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Filter: 'rsssl_le_ssl_path'
|
|||
|
|
* Can be used to change the ssl path where the ssl/keys directory
|
|||
|
|
* is created.
|
|||
|
|
*
|
|||
|
|
* @param string $sslPath
|
|||
|
|
* @param string $directoryPath
|
|||
|
|
* @return string
|
|||
|
|
*/
|
|||
|
|
return apply_filters('rsssl_le_ssl_path', $sslPath, $directoryPath);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if exists, create ssl/certs directory above the wp root if not existing
|
|||
|
|
* @return bool|string
|
|||
|
|
*/
|
|||
|
|
public function certs_directory()
|
|||
|
|
{
|
|||
|
|
$sslPath = $this->get_ssl_path();
|
|||
|
|
|
|||
|
|
if (!file_exists($sslPath)) {
|
|||
|
|
mkdir($sslPath, 0755 );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!file_exists( $sslPath . '/certs')) {
|
|||
|
|
mkdir( $sslPath . '/certs', 0755 );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (file_exists($sslPath . '/certs')) {
|
|||
|
|
return $sslPath . '/certs';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if exists, create ssl/keys directory above the wp root if not
|
|||
|
|
* @return bool|string
|
|||
|
|
*/
|
|||
|
|
public function key_directory()
|
|||
|
|
{
|
|||
|
|
$directory = $this->get_directory_path();
|
|||
|
|
$sslPath = $this->get_ssl_path();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$openbasedirHasRestrictions = $this->openbasedir_restriction($directory);
|
|||
|
|
|
|||
|
|
if ($openbasedirHasRestrictions === false) {
|
|||
|
|
if (!file_exists($sslPath) && is_writable($directory)) {
|
|||
|
|
mkdir($sslPath, 0755);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!file_exists($sslPath . '/keys') && is_writable($sslPath)) {
|
|||
|
|
mkdir($sslPath . '/keys', 0755);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (file_exists($sslPath . '/keys')) {
|
|||
|
|
return $sslPath . '/keys';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// If creating the folder has failed, we're on apache, and can write
|
|||
|
|
// to these folders, we create a root directory.
|
|||
|
|
$has_writing_permissions = $this->directory_has_writing_permissions($this->challenge_directory);
|
|||
|
|
|
|||
|
|
// We're guessing that if the challenge dir has writing permissions,
|
|||
|
|
// the new dir will also have it.
|
|||
|
|
if (RSSSL()->server->uses_htaccess() && $has_writing_permissions) {
|
|||
|
|
update_option('rsssl_create_folders_in_root', true, false);
|
|||
|
|
}
|
|||
|
|
} catch ( Exception $e ) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check for openbasedir restrictions
|
|||
|
|
*/
|
|||
|
|
private function openbasedir_restriction(string $path): bool
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
// Default error handler is required
|
|||
|
|
set_error_handler(null);
|
|||
|
|
|
|||
|
|
// Clean last error info.
|
|||
|
|
error_clear_last();
|
|||
|
|
|
|||
|
|
// Testing...
|
|||
|
|
@file_exists($path);
|
|||
|
|
|
|||
|
|
// Restore previous error handler
|
|||
|
|
restore_error_handler();
|
|||
|
|
|
|||
|
|
// Return `true` if error has occurred
|
|||
|
|
return ($error = error_get_last()) && $error['message'] !== '__clean_error_info';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Clear the keys directory, used in reset function
|
|||
|
|
* @since 5.0
|
|||
|
|
*/
|
|||
|
|
public function clear_keys_directory()
|
|||
|
|
{
|
|||
|
|
if (!rsssl_user_can_manage()) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$dir = $this->key_directory();
|
|||
|
|
$this->delete_files_directories_recursively( $dir );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @param $dir
|
|||
|
|
* Delete files and directories recursively. Used to clear the order from keys directory
|
|||
|
|
* @since 5.0.11
|
|||
|
|
*/
|
|||
|
|
private function delete_files_directories_recursively( $dir ) {
|
|||
|
|
if ( strpos( $dir, 'ssl/keys' ) !== false ) {
|
|||
|
|
foreach ( glob( $dir . '/*' ) as $file ) {
|
|||
|
|
if ( is_dir( $file ) ) {
|
|||
|
|
$this->delete_files_directories_recursively( $file );
|
|||
|
|
} else {
|
|||
|
|
unlink( $file );
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
rmdir( $dir );
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function maybe_create_htaccess_directories() {
|
|||
|
|
if (!rsssl_user_can_manage()) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( !RSSSL()->server->uses_htaccess() ) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( !get_option('rsssl_create_folders_in_root') ) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( !empty($this->get_directory_path()) ) {
|
|||
|
|
$this->write_htaccess_dir_file( $this->get_directory_path().'ssl/.htaccess' ,'ssl');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( !empty($this->key_directory()) ) {
|
|||
|
|
$this->write_htaccess_dir_file( trailingslashit($this->key_directory()).'.htaccess' ,'key');
|
|||
|
|
}
|
|||
|
|
if ( !empty($this->certs_directory()) ) {
|
|||
|
|
$this->write_htaccess_dir_file( trailingslashit($this->certs_directory()).'.htaccess' ,'certs');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function write_htaccess_dir_file($path, $type){
|
|||
|
|
$htaccess = '<ifModule mod_authz_core.c>' . "\n"
|
|||
|
|
. ' Require all denied' . "\n"
|
|||
|
|
. '</ifModule>' . "\n"
|
|||
|
|
. '<ifModule !mod_authz_core.c>' . "\n"
|
|||
|
|
. ' Deny from all' . "\n"
|
|||
|
|
. '</ifModule>';
|
|||
|
|
insert_with_markers($path, 'Really Simple Security LETS ENCRYPT', $htaccess);
|
|||
|
|
|
|||
|
|
$htaccess = file_get_contents( $path );
|
|||
|
|
if ( strpos($htaccess, 'deny from all') !== FALSE ) {
|
|||
|
|
update_option('rsssl_htaccess_file_set_'.$type, true, false);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if it's a subdomain multisite
|
|||
|
|
* @return RSSSL_RESPONSE
|
|||
|
|
*/
|
|||
|
|
public function is_subdomain_setup(){
|
|||
|
|
if ( !is_multisite() ) {
|
|||
|
|
$is_subdomain = false;
|
|||
|
|
} else {
|
|||
|
|
if ( defined('SUBDOMAIN_INSTALL') && SUBDOMAIN_INSTALL ) {
|
|||
|
|
$is_subdomain = true;
|
|||
|
|
} else {
|
|||
|
|
$is_subdomain = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($is_subdomain) {
|
|||
|
|
$status = 'error';
|
|||
|
|
$action = 'stop';
|
|||
|
|
$message = sprintf(__("This is a multisite configuration with subdomains. You should generate a wildcard certificate on the root domain.",'really-simple-ssl'), '<a href="'.rsssl_link('pro','error', 'letsencrypt').'" target="_blank">','</a>');
|
|||
|
|
rsssl_progress_remove('system-status');
|
|||
|
|
} else {
|
|||
|
|
$status = 'success';
|
|||
|
|
$action = 'continue';
|
|||
|
|
$message = __("No subdomain setup detected.","really-simple-ssl");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if we're about to create a wilcard certificate
|
|||
|
|
* @return bool
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
public function is_wildcard(){
|
|||
|
|
$subjects = $this->get_subjects();
|
|||
|
|
$is_wildcard = false;
|
|||
|
|
foreach ($subjects as $domain ) {
|
|||
|
|
if ( strpos($domain, '*') !== false ) {
|
|||
|
|
$is_wildcard = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $is_wildcard;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Check if the alias domain is available
|
|||
|
|
*
|
|||
|
|
* @return RSSSL_RESPONSE
|
|||
|
|
*/
|
|||
|
|
public function alias_domain_available(){
|
|||
|
|
if ( rsssl_is_subdomain() ) {
|
|||
|
|
return new RSSSL_RESPONSE('success', 'continue',__("Alias domain check is not relevant for a subdomain","really-simple-ssl"));
|
|||
|
|
}
|
|||
|
|
//write a test file to the uploads directory
|
|||
|
|
$uploads = wp_upload_dir();
|
|||
|
|
$upload_dir = trailingslashit($uploads['basedir']);
|
|||
|
|
$upload_url = trailingslashit($uploads['baseurl']);
|
|||
|
|
$file_content = false;
|
|||
|
|
$status_code = __('no response','really-simple-ssl');
|
|||
|
|
$domain = rsssl_get_domain();
|
|||
|
|
|
|||
|
|
if ( strpos( $domain, 'www.' ) !== false ) {
|
|||
|
|
$is_www = true;
|
|||
|
|
$alias_domain = str_replace( 'www.', '', $domain );
|
|||
|
|
} else {
|
|||
|
|
$is_www = false;
|
|||
|
|
$alias_domain = 'www.'.$domain;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( $is_www ) {
|
|||
|
|
$message = __("Please check if the non www version of your site also points to this website.", "really-simple-ssl" );
|
|||
|
|
} else {
|
|||
|
|
$message = __("Please check if the www version of your site also points to this website.", "really-simple-ssl" );
|
|||
|
|
}
|
|||
|
|
$error_message = __( "Could not verify alias domain.", "really-simple-ssl") .' '. $message.' '. __( "If this is not the case, don't add this alias to your certificate.", "really-simple-ssl");
|
|||
|
|
//get cached status first.
|
|||
|
|
$cached_status = get_transient('rsssl_alias_domain_available');
|
|||
|
|
if ( $cached_status ) {
|
|||
|
|
if ( $cached_status === 'available' ) {
|
|||
|
|
$status = 'success';
|
|||
|
|
$action = 'continue';
|
|||
|
|
$message = __( "Successfully verified alias domain.", "really-simple-ssl" );
|
|||
|
|
} else {
|
|||
|
|
$status = 'warning';
|
|||
|
|
$action = 'continue';
|
|||
|
|
$message = $error_message;
|
|||
|
|
}
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( ! file_exists( $upload_dir . 'rsssl' ) ) {
|
|||
|
|
mkdir( $upload_dir . 'rsssl', 0755 );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$test_string = 'file to test alias domain existence';
|
|||
|
|
$test_file = $upload_dir . 'rsssl/test.txt';
|
|||
|
|
file_put_contents($test_file, $test_string );
|
|||
|
|
$test_url = $upload_url . 'rsssl/test.txt';
|
|||
|
|
|
|||
|
|
|
|||
|
|
if ( ! file_exists( $test_file ) ) {
|
|||
|
|
$status = 'error';
|
|||
|
|
$action = 'stop';
|
|||
|
|
$message = __("Could not create test folder and file.", "really-simple-ssl").' '.
|
|||
|
|
__("Please create a folder 'rsssl' in the uploads directory, with 644 permissions.", "really-simple-ssl");
|
|||
|
|
} else {
|
|||
|
|
set_transient('rsssl_alias_domain_available', 'not-available', 30 * MINUTE_IN_SECONDS );
|
|||
|
|
$alias_test_url = str_replace( $domain, $alias_domain, $test_url );
|
|||
|
|
//always over http:
|
|||
|
|
$alias_test_url = str_replace('https://','http://', $alias_test_url);
|
|||
|
|
$response = wp_remote_get( $alias_test_url );
|
|||
|
|
if ( is_array( $response ) ) {
|
|||
|
|
$status_code = wp_remote_retrieve_response_code( $response );
|
|||
|
|
$file_content = wp_remote_retrieve_body( $response );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( $status_code !== 200 ) {
|
|||
|
|
$status = 'warning';
|
|||
|
|
$action = 'continue';
|
|||
|
|
$message = $error_message;
|
|||
|
|
if (intval($status_code) != 0 ) {
|
|||
|
|
$message .= ' '.sprintf( __( "Error code %s", "really-simple-ssl" ), $status_code );
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if ( ! is_wp_error( $response ) && ( strpos( $file_content, $test_string ) !== false ) ) {
|
|||
|
|
//make sure we only set this value once, during first setup.
|
|||
|
|
if ( !get_option('rsssl_initial_alias_domain_value_set') ) {
|
|||
|
|
rsssl_update_option('include_alias', true);
|
|||
|
|
update_option('rsssl_initial_alias_domain_value_set', true, false);
|
|||
|
|
}
|
|||
|
|
$status = 'success';
|
|||
|
|
$action = 'continue';
|
|||
|
|
$message = __( "Successfully verified alias domain.", "really-simple-ssl" );
|
|||
|
|
set_transient('rsssl_alias_domain_available', 'available', 30 * MINUTE_IN_SECONDS );
|
|||
|
|
} else {
|
|||
|
|
$status = 'warning';
|
|||
|
|
$action = 'continue';
|
|||
|
|
$message = $error_message;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Get string error from error message.
|
|||
|
|
* @param mixed|LE_ACME2\Exception\InvalidResponse $e
|
|||
|
|
*
|
|||
|
|
* @return string
|
|||
|
|
*/
|
|||
|
|
private function get_error($e){
|
|||
|
|
$is_raw_response = false;
|
|||
|
|
if (method_exists($e, 'getRawResponse') && isset($e->getRawResponse()->body['detail'])) {
|
|||
|
|
$is_raw_response = true;
|
|||
|
|
$error = $e->getRawResponse()->body['detail'];
|
|||
|
|
//check for subproblems
|
|||
|
|
if (isset($e->getRawResponse()->body['subproblems'])){
|
|||
|
|
$error .= '<ul>';
|
|||
|
|
foreach($e->getRawResponse()->body['subproblems'] as $index => $problem) {
|
|||
|
|
$error .= '<li>'. $this->cleanup_error_message($e->getRawResponse()->body['subproblems'][$index]['detail']).'</li>';
|
|||
|
|
}
|
|||
|
|
$error .= '</ul>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} else {
|
|||
|
|
$error = $e->getMessage();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
$max = strpos($error, 'CURL response');
|
|||
|
|
if ($max===false) {
|
|||
|
|
$max = 200;
|
|||
|
|
}
|
|||
|
|
if (!$is_raw_response){
|
|||
|
|
$error = substr( $error, 0, $max);
|
|||
|
|
}
|
|||
|
|
return $error;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Generic SSL cert installation function
|
|||
|
|
*
|
|||
|
|
* @return RSSSL_RESPONSE
|
|||
|
|
*/
|
|||
|
|
public function cron_renew_installation() {
|
|||
|
|
$install_method = get_option('rsssl_le_certificate_installed_by_rsssl');
|
|||
|
|
$data = explode(':', $install_method );
|
|||
|
|
|
|||
|
|
$server = isset($data[0]) ? $data[0] : false;
|
|||
|
|
$type = isset($data[1]) ? $data[1] : false;
|
|||
|
|
|
|||
|
|
$attempt_count = (int) get_transient( 'rsssl_le_install_attempt_count' );
|
|||
|
|
$attempt_count++;
|
|||
|
|
set_transient('rsssl_le_install_attempt_count', $attempt_count, DAY_IN_SECONDS);
|
|||
|
|
if ( $attempt_count>10 ){
|
|||
|
|
delete_option("rsssl_le_start_installation");
|
|||
|
|
$status = 'error';
|
|||
|
|
$action = 'stop';
|
|||
|
|
$message = __("The certificate installation was rate limited. Please try again later.",'really-simple-ssl');
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (rsssl_is_ready_for('installation')) {
|
|||
|
|
try {
|
|||
|
|
if ( $server === 'cpanel' ) {
|
|||
|
|
if ($type==='default') {
|
|||
|
|
$response = rsssl_install_cpanel_default();
|
|||
|
|
} else if ( function_exists('rsssl_shell_installSSL') ) {
|
|||
|
|
$response = rsssl_shell_installSSL();
|
|||
|
|
} else {
|
|||
|
|
//in case of auto ssl.
|
|||
|
|
$response = new RSSSL_RESPONSE('error', 'stop', '');
|
|||
|
|
delete_option( "rsssl_le_start_installation" );
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ( $response->status === 'success' ) {
|
|||
|
|
delete_option( "rsssl_le_start_installation" );
|
|||
|
|
}
|
|||
|
|
return $response;
|
|||
|
|
} else if ( $server === 'plesk') {
|
|||
|
|
$response = rsssl_plesk_install();
|
|||
|
|
if ( $response->status === 'success' ) {
|
|||
|
|
delete_option( "rsssl_le_start_installation" );
|
|||
|
|
}
|
|||
|
|
return $response;
|
|||
|
|
} else {
|
|||
|
|
$status = 'error';
|
|||
|
|
$action = 'stop';
|
|||
|
|
$message = __("Not recognized server.", "really-simple-ssl");
|
|||
|
|
}
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
$status = 'error';
|
|||
|
|
$action = 'stop';
|
|||
|
|
$message = __("Installation failed.", "really-simple-ssl");
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
$status = 'error';
|
|||
|
|
$action = 'stop';
|
|||
|
|
$message = __("The system is not ready for the installation yet. Please run the wizard again.", "really-simple-ssl");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return new RSSSL_RESPONSE($status, $action, $message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function upgrade(){
|
|||
|
|
if ( get_option('rsssl_upgrade_le_key') ) {
|
|||
|
|
// Check if the encryption key is not empty before upgrading. On slow servers, the write to wp-config.php can be
|
|||
|
|
// incomplete before the plugin gets here
|
|||
|
|
$key = $this->get_encryption_key();
|
|||
|
|
if ( empty( $key ) ) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
delete_option('rsssl_upgrade_le_key');
|
|||
|
|
$site_key = get_site_option( 'rsssl_le_key');
|
|||
|
|
if ( $site_key ) {
|
|||
|
|
$options = [
|
|||
|
|
'directadmin_password',
|
|||
|
|
'cpanel_password',
|
|||
|
|
'cloudways_api_key',
|
|||
|
|
'plesk_password',
|
|||
|
|
];
|
|||
|
|
foreach ( $options as $option ) {
|
|||
|
|
$option_value = rsssl_get_option( $option );
|
|||
|
|
if ( $option_value ) {
|
|||
|
|
$decrypted = $this->decrypt_if_prefixed( $option_value, 'rsssl_', $site_key);
|
|||
|
|
$encrypted = $this->encrypt_with_prefix($decrypted);
|
|||
|
|
rsssl_update_option($option, $encrypted);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
delete_site_option( 'rsssl_le_key');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Cleanup the default message a bit
|
|||
|
|
*
|
|||
|
|
* @param $msg
|
|||
|
|
*
|
|||
|
|
* @return string|string[]
|
|||
|
|
*/
|
|||
|
|
private function cleanup_error_message($msg){
|
|||
|
|
return str_replace(array(
|
|||
|
|
'Refer to sub-problems for more information.',
|
|||
|
|
'Error creating new order ::',
|
|||
|
|
), '', $msg);
|
|||
|
|
}
|
|||
|
|
}
|