Initial commit: Atomaste website

This commit is contained in:
2025-12-10 12:17:30 -05:00
commit 0b9e5d1605
19260 changed files with 5206382 additions and 0 deletions

View File

@@ -0,0 +1,281 @@
<?php defined( 'ABSPATH' ) or die();
if ( ! class_exists( "rsssl_le_restapi" ) ) {
class rsssl_le_restapi{
private static $_this;
function __construct() {
if ( isset( self::$_this ) ) {
wp_die( sprintf( '%s is a singleton class and you cannot create a second instance.',
get_class( $this ) ) );
}
self::$_this = $this;
add_filter("rsssl_run_test", array($this, 'handle_lets_encrypt_request'), 10, 3);
add_action( 'rsssl_after_save_field', array( $this, 'after_save_field' ), 10, 4 );
}
static function this() {
return self::$_this;
}
/**
* Switch to DNS verification
* @param array $data
* @return []
*/
public function update_verification_type($data){
$type = $data['id'];
$type = $type === 'dns' ? 'dns' : 'dir';
rsssl_update_option('verification_type', $type );
//register this manual change, so we don't force change it back.
update_option('rsssl_manually_changed_verification_type', true, false);
if ($type==='dns') {
rsssl_progress_add('directories');
} else {
rsssl_progress_add('dns-verification');
}
return new RSSSL_RESPONSE(
'success',
'stop',
''
);
}
/**
* Skip DNS check
* @return RSSSL_RESPONSE
*/
public function skip_dns_check(){
if ( !rsssl_user_can_manage() ) {
return new RSSSL_RESPONSE(
'error',
'stop',
''
);
}
update_option('rsssl_skip_dns_check', true, false);
return new RSSSL_RESPONSE(
'success',
'stop',
''
);
}
/**
* Get installation data
* @return RSSSL_RESPONSE
*/
public function installation_data(){
if ( !rsssl_user_can_manage() ) {
return new RSSSL_RESPONSE(
'error',
'stop',
''
);
}
$key_file = get_option('rsssl_private_key_path');
$cert_file = get_option('rsssl_certificate_path');
$cabundle_file = get_option('rsssl_intermediate_path');
$data = [
'generated_by_rsssl' => rsssl_generated_by_rsssl(),
'download_url' => rsssl_le_url.'download.php?token='.wp_create_nonce('rsssl_download_cert'),
'key_content' => file_exists($key_file) ? file_get_contents($key_file) : 'no data found',
'certificate_content' => file_exists($cert_file) ? file_get_contents($cert_file) : 'no data found',
'ca_bundle_content' => file_exists($cabundle_file) ? file_get_contents($cabundle_file) : 'no data found',
];
return new RSSSL_RESPONSE(
'success',
'continue',
'',
$data
);
}
/**
* Challenge directory request
*
* @return RSSSL_RESPONSE
*/
public function skip_challenge_directory_request(){
if ( !rsssl_user_can_manage() ) {
return new RSSSL_RESPONSE(
'error',
'stop',
''
);
}
update_option('rsssl_skip_challenge_directory_request', true, false);
return new RSSSL_RESPONSE(
'success',
'stop',
''
);
}
/**
* Reset the LE wizard
* @return bool[]|RSSSL_RESPONSE
*/
public function reset() {
if ( !rsssl_user_can_manage() ) {
return new RSSSL_RESPONSE(
'success',
'stop',
''
);
}
RSSSL_LE()->letsencrypt_handler->clear_order(true);
rsssl_update_option('verification_type', 'dir' );
delete_option('rsssl_skip_dns_check' );
delete_option('rsssl_skip_challenge_directory_request' );
delete_option('rsssl_create_folders_in_root');
delete_option('rsssl_hosting_dashboard');
delete_option('rsssl_manually_changed_verification_type');
return new RSSSL_RESPONSE(
'success',
'stop',
''
);
}
public function clean_up(){
//clean up stored pw, if requested
RSSSL_LE()->letsencrypt_handler->cleanup_on_ssl_activation();
}
/**
* Process a Let's Encrypt test request
*
* @param array $response
* @param string $test
* @param WP_REST_Request $request
*
* @return RSSSL_RESPONSE|array
*/
public function handle_lets_encrypt_request($response, $test, $data){
if ( ! current_user_can('manage_security') ) {
return new RSSSL_RESPONSE(
'error',
'stop',
__( "Permission denied.", 'really-simple-ssl' )
);
}
switch( $test ){
case 'reset':
return $this->reset();
case 'update_verification_type':
return $this->update_verification_type($data);
case 'skip_dns_check':
return $this->skip_dns_check();
case 'skip_challenge_directory_request':
return $this->skip_challenge_directory_request();
case 'installation_data':
return $this->installation_data();
case 'is_subdomain_setup':
case 'verify_dns':
case 'certificate_status':
case 'curl_exists':
case 'server_software':
case 'alias_domain_available':
case 'check_domain':
case 'check_host':
case 'check_challenge_directory':
case 'check_key_directory':
case 'check_certs_directory':
case 'check_writing_permissions':
case 'challenge_directory_reachable':
case 'get_account':
case 'get_dns_token':
case 'terms_accepted':
case 'create_bundle_or_renew':
case 'search_ssl_installation_url':
case 'rsssl_install_cpanel_autossl':
case 'rsssl_cpanel_set_txt_record':
case 'rsssl_install_cpanel_default':
case 'rsssl_cloudways_server_data':
case 'rsssl_cloudways_install_ssl':
case 'rsssl_cloudways_auto_renew':
case 'rsssl_install_directadmin':
case 'rsssl_plesk_install':
case 'cleanup_on_ssl_activation':
return $this->get_installation_progress($response, $test, $data);
default:
return $response;
}
}
/**
* Run a LE test
* @param $response
* @param $function
* @param $data
*
* @return RSSSL_RESPONSE
*/
public function get_installation_progress( $response, $function, $data ){
$id = $data['id'];
if ( ! current_user_can('manage_security') ) {
return new RSSSL_RESPONSE(
'error',
'stop',
__( "Permission denied.", 'really-simple-ssl' )
);
}
if (!function_exists($function) && !method_exists(RSSSL_LE()->letsencrypt_handler, $function)) {
return new RSSSL_RESPONSE(
'error',
'stop',
__( "Test not found.", 'really-simple-ssl' )
);
}
rsssl_progress_add($id);
if ( function_exists($function) ){
$response = $function();
} else {
$response = RSSSL_LE()->letsencrypt_handler->$function();
}
return $response;
}
/**
* Handle some custom options after saving the wizard options
* @param string $field_id
* @param mixed $field_value
* @param mixed $prev_value
* @param string $type
*/
public function after_save_field( $field_id, $field_value, $prev_value, $type ) {
//only run when changes have been made
if ( $field_value === $prev_value ) {
return;
}
if ( $field_id==='other_host_type'){
if ( isset(RSSSL_LE()->hosts->hosts[$field_value]) ){
$dashboard = RSSSL_LE()->hosts->hosts[$field_value]['hosting_dashboard'];
update_option('rsssl_hosting_dashboard', $dashboard, false);
} else {
update_option('rsssl_hosting_dashboard', false, false);
}
}
if ( $field_id === 'email_address'&& is_email($field_value) ) {
RSSSL_LE()->letsencrypt_handler->update_account($field_value);
}
}
}
} //class closure

View File

@@ -0,0 +1,6 @@
{
"require": {
"fbett/le_acme2": "^1.5",
"plesk/api-php-lib": "^1.0"
}
}

View File

@@ -0,0 +1,658 @@
<?php
defined( 'ABSPATH' ) or die( );
if ( ! class_exists( "rsssl_le_hosts" ) ) {
class rsssl_le_hosts {
private static $_this;
public $steps;
public $hosts;
public $supported_hosts;
public $not_local_certificate_hosts;
public $no_installation_renewal_needed;
public $dashboard_activation_required;
public $activated_by_default;
public $paid_only;
public function __construct() {
if ( !defined('RSSSL_LE_CONFIG_LOADED') ) {
define('RSSSL_LE_CONFIG_LOADED', true);
}
if ( isset( self::$_this ) ) {
wp_die( 'This is a singleton class and you cannot create a second instance.');
}
self::$_this = $this;
/**
* Plesk requires local SSL generation, and installation renewal.
* Cpanel default requires local SSL generation, and installation renewal.
* Cpanel autossl: no local ssl generation, no renewal
*/
$this->hosts = array(
'cloudways' => array(
'name' => 'CloudWays',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => false,
'hosting_dashboard' => 'cloudways',
'api' => true,
'ssl_installation_link' => false,
),
'tierpoint' => array(
'name' => 'TierPoint',
'installation_renewal_required' => true,
'local_ssl_generation_needed' => true,
'free_ssl_available' => false,
'hosting_dashboard' => 'cpanel',
'api' => true,
'ssl_installation_link' => false,
),
'godaddy' => array(
'name' => 'GoDaddy',
'installation_renewal_required' => true,
'local_ssl_generation_needed' => true,
'free_ssl_available' => false,
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => false,
),
'godaddy_managed' => array(
'name' => 'GoDaddy Managed WordPress',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activated_by_default',
'hosting_dashboard' => 'godaddymanaged',
'api' => false,
'ssl_installation_link' => false,
),
'kasserver' => array(
'name' => 'Kasserver',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => 'kasserver',
'api' => false,
'ssl_installation_link' => 'https://kas.all-inkl.com/',
),
'argeweb' => array(
'name' => 'Argeweb',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => 'plesk',
'api' => false,
'ssl_installation_link' => 'https://www.argeweb.nl/argecs/',
),
'hostgator' => array(
'name' => 'HostGator',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => true,
'hosting_dashboard' => 'cpanel:autossl',
'api' => true,
'ssl_installation_link' => 'https://{host}:2083/frontend/paper_lantern/security/tls_status/',
),
'ionos' => array(
'name' => 'IONOS',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'paid_only',
'hosting_dashboard' => 'ionos',
'api' => false,
'ssl_installation_link' => '',
),
'simply' => array(
'name' => 'Simply',
'installation_renewal_required' => true,
'local_ssl_generation_needed' => true,
'free_ssl_available' => false,
'hosting_dashboard' => false,
'api' => false,
'ssl_installation_link' => 'https://www.simply.com/en/controlpanel/sslcerts/',
),
'siteground' => array(
'name' => 'SiteGround',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => false,
'api' => false,
'ssl_installation_link' => 'https://tools.siteground.com/ssl',
),
'dreamhost' => array(
'name' => 'Dreamhost',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => false,
'api' => false,
'ssl_installation_link' => 'https://help.dreamhost.com/hc/en-us/articles/216539548-Adding-a-free-Let-s-Encrypt-certificate',
),
'wpengine' => array(
'name' => 'WPEngine',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => false,
'api' => false,
'detected' => isset($_SERVER['IS_WPE']),
'ssl_installation_link' => 'https://wpengine.com/support/add-ssl-site/#letsencrypt',
),
'ipage' => array(
'name' => 'iPage',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activated_by_default',
'hosting_dashboard' => false,
'api' => false,
'ssl_installation_link' => 'https://www.ipage.com/help/article/enable-your-free-ssl-certificate',
),
'onecom' => array(
'name' => 'one.com',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activated_by_default',
'hosting_dashboard' => false,
'api' => false,
'ssl_installation_link' => 'https://help.one.com/hc/en-us/articles/360000297458-Why-is-SSL-HTTPS-not-working-on-my-site-',
),
'wpmudev' => array(
'name' => 'WPMUDEV',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activated_by_default',
'hosting_dashboard' => false,
'api' => false,
'ssl_installation_link' => 'https://wpmudev.com',
),
'ovh' => array(
'name' => 'OVH',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activated_by_default',
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => 'https://ovh.com',
),
'bluehost' => array(
'name' => 'BlueHost',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activated_by_default',
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => 'https://www.bluehost.com/help/article/how-to-activate-a-free-wordpress-ssl',
),
'freeola' => array(
'name' => 'Freeola',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'paid_only',
'hosting_dashboard' => 'freeola',
'api' => false,
'ssl_installation_link' => '',
),
'hostinger' => array(
'name' => 'Hostinger',
'installation_renewal_required' => true,
'local_ssl_generation_needed' => true,
'free_ssl_available' => false,
'hosting_dashboard' => 'hpanel',
'api' => false,
'ssl_installation_link' => 'https://hpanel.hostinger.com/hosting/{domain}advanced/ssl',
),
'pcextreme' => array(
'name' => 'PCExtreme',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activated_by_default',
'hosting_dashboard' => 'directadmin',
'api' => false,
'ssl_installation_link' => 'https://help.pcextreme.nl/domains-ssl/hoe-vraag-ik-een-ssl-certificaat-aan-voor-mijn-domein/',
),
'internic' => array(
'name' => 'Internic',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activated_by_default',
'hosting_dashboard' => 'internic',
'api' => false,
'ssl_installation_link' => 'https://internic.com',
),
'aruba' => array(
'name' => 'Aruba',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'paid_only',
'hosting_dashboard' => 'aruba',
'api' => false,
'ssl_installation_link' => 'https://admin.aruba.it/PannelloAdmin/UI/Pages/ContentSection.aspx?Action=153',
),
'namecheap' => array(
'name' => 'Namecheap',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => 'https://www.namecheap.com/blog/install-free-ssls/',
),
'hostpapa' => array(
'name' => 'Hostpapa',
'installation_renewal_required' => true,
'local_ssl_generation_needed' => true,
'free_ssl_available' => false,
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => false,
),
'webcom' => array(
'name' => 'web.com',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'paid_only',
'hosting_dashboard' => 'web.com',
'api' => false,
'ssl_installation_link' => false,
),
'crazydomains' => array(
'name' => 'Crazydomains',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'paid_only',
'hosting_dashboard' => 'crazydomains',
'api' => false,
'ssl_installation_link' => false,
),
'strato' => array(
'name' => 'Strato',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => 'plesk',
'api' => false,
'ssl_installation_link' => false,
),
'inmotion' => array(
'name' => 'Inmotion',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => 'https://www.inmotionhosting.com/support/website/ssl/auto-ssl-guide/',
),
'flywheel' => array(
'name' => 'Flywheel',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => 'flywheel',
'api' => false,
'detected' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Flywheel/') === 0,
'ssl_installation_link' => 'https://getflywheel.com/why-flywheel/simple-ssl/',
),
'kinsta' => array(
'name' => 'Kinsta',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => 'mykinsta',
'api' => false,
'ssl_installation_link' => false,
),
'pressable' => array(
'name' => 'Pressable',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'paid_only',
'hosting_dashboard' => 'pressable',
'api' => false,
'ssl_installation_link' => false,
'detected' => (defined('IS_ATOMIC') && IS_ATOMIC) || (defined('IS_PRESSABLE') && IS_PRESSABLE),
),
'wpx' => array(
'name' => 'WPX',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => 'wpx',
'api' => false,
'ssl_installation_link' => false,
),
'greengeeks' => array(
'name' => 'Greengeeks',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => 'greengeeks',
'api' => false,
'ssl_installation_link' => 'https://www.greengeeks.com/support/article/getting-started-adding-lets-encrypt-ssl-greengeeks-account/',
),
'liquidweb' => array(
'name' => 'Liquidweb',
'installation_renewal_required' => true,
'local_ssl_generation_needed' => true,
'free_ssl_available' => false,
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => false,
),
'profreehost' => array(
'name' => 'Profreehost',
'installation_renewal_required' => true,
'local_ssl_generation_needed' => true,
'free_ssl_available' => false,
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => 'https://profreehost.com/support/ssl-https/how-to-install-an-ssl-certificate/',
),
'hostdash' => array(
'name' => 'Hostdash',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => false,
),
'byethost' => array(
'name' => 'Byethost',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activated_by_default',
'hosting_dashboard' => 'byethost',
'api' => false,
'ssl_installation_link' => false,
),
'site5' => array(
'name' => 'Site5',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'paid_only',
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => '',
),
'epizy' => array(
'name' => 'Epizy',
'installation_renewal_required' => true,
'local_ssl_generation_needed' => true,
'free_ssl_available' => false,
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => '',
),
'infinityfree' => array(
'name' => 'Infinityfree',
'installation_renewal_required' => true,
'local_ssl_generation_needed' => true,
'free_ssl_available' => false,
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => '',
),
'gandi' => array(
'name' => 'Gandi',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => 'gandi',
'api' => false,
'ssl_installation_link' => false,
),
'contabo' => array(
'name' => 'Contabo',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => true,
'hosting_dashboard' => 'cpanel:autossl',
'api' => true,
'ssl_installation_link' => 'https://{host}:2083/frontend/paper_lantern/security/tls_status/',
),
'earthlink' => array(
'name' => 'Earthlink',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => false,
'hosting_dashboard' => 'cpanel',
'api' => true,
'ssl_installation_link' => false,
),
'hostway' => array(
'name' => 'Hostway',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activated_by_default',
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => false,
),
'beget' => array(
'name' => 'Beget',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => 'beget',
'api' => false,
'ssl_installation_link' => false,
),
'fatcow' => array(
'name' => 'Fatcow',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => 'fatcow',
'api' => false,
'ssl_installation_link' => false,
),
'ventraip' => array(
'name' => 'Ventraip',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activated_by_default',
'hosting_dashboard' => 'cpanel:autossl',
'api' => false,
'ssl_installation_link' => 'https://{host}:2083/frontend/paper_lantern/security/tls_status/',
),
'namescouk' => array(
'name' => 'Names.co.uk',
'installation_renewal_required' => true,
'local_ssl_generation_needed' => true,
'free_ssl_available' => false,
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => false,
),
'mediatemple' => array(
'name' => 'Mediatemple',
'installation_renewal_required' => true,
'local_ssl_generation_needed' => true,
'free_ssl_available' => false,
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => false,
),
'xxl' => array(
'name' => 'XXL Hosting',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => true,
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => false,
),
'combell' => array(
'name' => 'Combell',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => true,
'hosting_dashboard' => 'cpanel',
'detected' => defined('HBRW_PLATFORM_ID') && (int) HBRW_PLATFORM_ID === 1,
'api' => false,
'ssl_installation_link' => false,
),
'easyhost' => array(
'name' => 'EasyHost',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => true,
'hosting_dashboard' => 'cpanel',
'detected' => defined('HBRW_PLATFORM_ID') && (int) HBRW_PLATFORM_ID === 2,
'api' => false,
'ssl_installation_link' => false,
),
'transip' => array(
'name' => 'TransIP',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => true,
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => false,
),
'digitalocean' => array(
'name' => 'Digitalocean',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => 'digitalocean',
'api' => false,
'ssl_installation_link' => 'https://docs.digitalocean.com/products/accounts/security/certificates/',
),
'fisthost' => array(
'name' => 'Fisthost',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => 'fisthost',
'api' => false,
'ssl_installation_link' => 'https://my.fisthost.com/knowledgebase/6/How-do-I-activate-my-free-SSL.html',
),
'register' => array(
'name' => 'register.lk',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'paid_only',
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => '',
),
'fasthosts' => array(
'name' => 'Fasthosts',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => 'cpanel',
'api' => false,
'ssl_installation_link' => false,
),
'upress' => array(
'name' => 'Upress',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => 'false',
'api' => false,
'ssl_installation_link' => 'https://support.upress.io',
),
'infomaniak' => array(
'name' => 'Infomaniak',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_required',
'hosting_dashboard' => 'infomaniak',
'api' => false,
'ssl_installation_link' => 'https://www.infomaniak.com/en/secure/ssl-certificates',
),
'dandomain' => array(
'name' => 'DanDomain',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'paid_only',
'hosting_dashboard' => 'dandomain',
'api' => false,
'ssl_installation_link' => '',
),
'hetzner' => array(
'name' => 'Hetzner',
'installation_renewal_required' => false,
'local_ssl_generation_needed' => false,
'free_ssl_available' => 'activation_reguired',
'hosting_dashboard' => 'konsoleh',
'api' => false,
'ssl_installation_link' => '',
),
);
$this->not_local_certificate_hosts = $this->filter_hosts( 'local_ssl_generation_needed', false);
$this->dashboard_activation_required = $this->filter_hosts( 'free_ssl_available', 'activation_required');
$this->activated_by_default = $this->filter_hosts( 'free_ssl_available', 'activated_by_default');
$this->paid_only = $this->filter_hosts( 'free_ssl_available', 'paid_only');
$this->no_installation_renewal_needed = $this->filter_hosts( 'installation_renewal_required', false);
$this->no_installation_renewal_needed[] = 'cpanel:autossl';
ksort($this->hosts);
$this->supported_hosts = array(
'none' => __('I don\'t know, or not listed, proceed with installation', 'really-simple-ssl'),
);
$this->supported_hosts = $this->supported_hosts + wp_list_pluck($this->hosts, 'name');
}
static function this() {
return self::$_this;
}
public function detect_host_on_activation(){
foreach ( $this->hosts as $host_key => $host ) {
if ( isset($host['detected']) && $host['detected'] ) {
rsssl_update_option('other_host_type', $host_key );
}
}
}
/**
* @param array $array
* @param mixed $filter_value
* @param mixed $filter_key
*
* @return array
*/
public function filter_hosts( $filter_key, $filter_value){
return array_keys(array_filter($this->hosts, function ($var) use ($filter_value, $filter_key) {
return ($var[$filter_key] == $filter_value);
}) );
}
/**
* @param string | bool $type
*
* @return bool
*/
public function host_api_supported( $type ) {
$hosting_company = rsssl_get_other_host();
//if not listed, we assume it can.
if ( !$hosting_company || $hosting_company === 'none' ) {
return true;
}
$hosts_has_dashboard = RSSSL_LE()->hosts->filter_hosts( 'api', $type);
if ( in_array($hosting_company, $hosts_has_dashboard) ) {
return true;
} else {
return false;
}
}
}
} //class closure

View File

@@ -0,0 +1,561 @@
<?php
function rsssl_le_steps(){
$steps =
[
[
"id" => "system-status",
"title" => __( "System status", 'really-simple-ssl' ),
],
[
"id" => "domain",
"title" => __( "General Settings", 'really-simple-ssl' ),
],
[
"id" => "directories",
"title" => __( "Directories", 'really-simple-ssl' ),
],
[
"id" => "dns-verification",
"title" => __( "DNS verification", 'really-simple-ssl' ),
],
[
"id" => "generation",
"title" => __( "Generation", 'really-simple-ssl' ),
],
[
"id" => "installation",
"title" => __( "Installation", 'really-simple-ssl' ),
],
];
return $steps;
}
/**
* Let's Encrypt
*/
add_filter("rsssl_fields", "rsssl_le_add_fields");
function rsssl_le_add_fields($fields) {
$fields = array_merge($fields, [
[
'id' => 'system-status',
'menu_id' => 'le-system-status',
'group_id' => 'le-system-status',
"intro" => __( "Detected status of your setup.", "really-simple-ssl" ),
'type' => 'letsencrypt',
'default' => false,
'actions' => [
[
'description' => __( "Checking SSL certificate...", "really-simple-ssl" ),
'action' => 'certificate_status',
'attempts' => 1,
'status' => 'inactive',
],
[
'description' => __( "Checking if CURL is available...", "really-simple-ssl" ),
'action' => 'curl_exists',
'attempts' => 1,
'status' => 'inactive',
],
[
'description' => __( "Checking server software...", "really-simple-ssl" ),
'action' => 'server_software',
'attempts' => 1,
'status' => 'inactive',
],
[
'description' => __( "Checking alias domain...", "really-simple-ssl" ),
'action' => 'alias_domain_available',
'attempts' => 3,
'status' => 'inactive',
],
[
'description' => __( "Checking for website configuration...", "really-simple-ssl" ),
'action' => 'check_domain',
'attempts' => 1,
'status' => 'inactive',
],
],
],
[
'id' => 'verification_type',
'menu_id' => 'le-general',
'group_id' => 'le-general',
'type' => 'hidden',
],
[
'id' => 'email_address',
'menu_id' => 'le-general',
'group_id' => 'le-general',
'type' => 'email',
'label' => __( "Email address", 'really-simple-ssl' ),
'help' => [
'label' => 'default',
'title' => __( "Email address", "really-simple-ssl" ),
'text' => __( "This email address is used to create a Let's Encrypt account. This is also where you will receive renewal notifications.", 'really-simple-ssl' ),
],
'default' => '',
'required' => true,
],
[
'id' => 'accept_le_terms',
'menu_id' => 'le-general',
'group_id' => 'le-general',
'type' => 'checkbox',
'default' => false,
'required' => true,
'label' => __( 'I agree to the Terms & Conditions from Let\'s Encrypt.','really-simple-ssl'),
'comment' => '<a target="_blank" href="https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf">'.__('Terms & Conditions', "really-simple-ssl" ).'</a>',
],
[
'id' => 'disable_ocsp',
'menu_id' => 'le-general',
'group_id' => 'le-general',
'required'=> false,
'type' => 'checkbox',
'default' => false,
'help' => [
'label' => 'default',
'url' => 'ocsp-stapling',
'title' => __( "Disable OCSP stapling", "really-simple-ssl" ),
'text' => __( "OCSP stapling is configured as enabled by default. You can disable this option if this is not supported by your hosting provider.", "really-simple-ssl" ),
],
'label' => __( "Disable OCSP stapling", 'really-simple-ssl' ),
],
[
'id' => 'domain',
'menu_id' => 'le-general',
'group_id' => 'le-general',
'type' => 'text',
'default' => rsssl_get_domain(),
'label' => __( "Domain", 'really-simple-ssl' ),
'required' => false,
'disabled' => true,
],
[
'id' => 'include_alias',
'menu_id' => 'le-general',
'group_id' => 'le-general',
'type' => 'checkbox',
'default' => '',
'label' => __( "Include alias", 'really-simple-ssl' ),
'help' => [
'label' => 'default',
'title' => __( "Include alias", "really-simple-ssl" ),
'text' => __( "This will include both the www. and non-www. version of your domain.", "really-simple-ssl" ) . ' '
. __( "You should have the www domain pointed to the same website as the non-www domain.", 'really-simple-ssl' ),
],
'server_conditions' => [
'relation' => 'AND',
[
'rsssl_is_subdomain()' => false,
'rsssl_wildcard_certificate_required()' => false,
]
],
],
[
'id' => 'cpanel_host',
'menu_id' => 'le-hosting',
'group_id' => 'le-hosting',
'type' => 'text',
'default' => '',
'label' => __( "CPanel host", 'really-simple-ssl' ),
'help' => [
'label' => 'default',
'title' => __( "CPanel host", "really-simple-ssl" ),
'text' => __( "The URL you use to access your cPanel dashboard. Ends on :2083.", 'really-simple-ssl' ),
],
'required' => false,
'disabled' => false,
'server_conditions' => [
'relation' => 'AND',
[
'rsssl_is_cpanel()' => true,
'rsssl_activated_by_default()' => false,
'rsssl_activation_required()' => false,
'rsssl_paid_only()' => false,
]
],
],
[
'id' => 'cpanel_username',
'menu_id' => 'le-hosting',
'group_id' => 'le-hosting',
'type' => 'text',
'default' => '',
'label' => __( "CPanel username", 'really-simple-ssl' ),
'required' => false,
'disabled' => false,
'server_conditions' => [
'relation' => 'AND',
[
'rsssl_cpanel_api_supported()' => true,
'rsssl_activated_by_default()' => false,
'rsssl_activation_required()' => false,
'rsssl_paid_only()' => false,
]
],
],
[
'id' => 'cpanel_password',
'menu_id' => 'le-hosting',
'group_id' => 'le-hosting',
'type' => 'password',
'default' => '',
'label' => __( "CPanel password", 'really-simple-ssl' ),
'required' => false,
'disabled' => false,
'server_conditions' => [
'relation' => 'AND',
[
'rsssl_cpanel_api_supported()' => true,
'rsssl_activated_by_default()' => false,
'rsssl_activation_required()' => false,
'rsssl_paid_only()' => false,
]
],
],
[
'id' => 'directadmin_host',
'menu_id' => 'le-hosting',
'group_id' => 'le-hosting',
'type' => 'text',
'default' => '',
'label' => __( "DirectAdmin host", 'really-simple-ssl' ),
'help' => [
'label' => 'default',
'title' => __( "Direct Admin URL", "really-simple-ssl" ),
'text' => __( "The URL you use to access your DirectAdmin dashboard. Ends on :2222.", 'really-simple-ssl' ),
],
'required' => false,
'disabled' => false,
'server_conditions' => [
'relation' => 'AND',
[
'rsssl_is_directadmin()' => true,
'rsssl_activated_by_default()' => false,
'rsssl_activation_required()' => false,
'rsssl_paid_only()' => false,
]
],
],
[
'id' => 'directadmin_username',
'menu_id' => 'le-hosting',
'group_id' => 'le-hosting',
'type' => 'text',
'default' => '',
'label' => __( "DirectAdmin username", 'really-simple-ssl' ),
'required' => false,
'disabled' => false,
'server_conditions' => [
'relation' => 'AND',
[
'rsssl_is_directadmin()' => true,
'rsssl_activated_by_default()' => false,
'rsssl_activation_required()' => false,
'rsssl_paid_only()' => false,
]
],
],
[
'id' => 'directadmin_password',
'menu_id' => 'le-hosting',
'group_id' => 'le-hosting',
'type' => 'password',
'default' => '',
'label' => __( "DirectAdmin password", 'really-simple-ssl' ),
'required' => false,
'disabled' => false,
'server_conditions' => [
'relation' => 'AND',
[
'rsssl_is_directadmin()' => true,
'rsssl_activated_by_default()' => false,
'rsssl_activation_required()' => false,
'rsssl_paid_only()' => false,
]
],
],
[
'id' => 'cloudways_user_email',
'menu_id' => 'le-hosting',
'group_id' => 'le-hosting',
'type' => 'text',
'default' => '',
'placeholder' => 'email@email.com',
'label' => __( "CloudWays user email", 'really-simple-ssl' ),
'required' => false,
'disabled' => false,
'server_conditions' => [
'relation' => 'AND',
[
'other_host_type' => 'cloudways',
]
],
],
[
'id' => 'cloudways_api_key',
'menu_id' => 'le-hosting',
'group_id' => 'le-hosting',
'type' => 'password',
'default' => '',
'label' => __( "CloudWays API key", 'really-simple-ssl' ),
'required' => false,
'disabled' => false,
'help' => [
'label' => 'default',
'title' => __( "CloudWays API key", "really-simple-ssl" ),
'text' => sprintf( __( "You can find your api key %shere%s (make sure you're logged in with your main account).", "really-simple-ssl" ),
'<a target="_blank" href="https://platform.cloudways.com/api">', '</a>' ),
],
'server_conditions' => [
'relation' => 'AND',
[
'other_host_type' => 'cloudways',
]
],
],
[
'id' => 'plesk_host',
'menu_id' => 'le-hosting',
'group_id' => 'le-hosting',
'type' => 'text',
'default' => '',
'label' => __( "Plesk host", 'really-simple-ssl' ),
'help' => [
'label' => 'default',
'title' => __( "Plesk admin URL", "really-simple-ssl" ),
'text' => __( "The URL you use to access your Plesk dashboard. Ends on :8443.", 'really-simple-ssl' ),
],
'required' => false,
'disabled' => false,
'server_conditions' => [
'relation' => 'AND',
[
'rsssl_is_plesk()' => true,
'rsssl_activated_by_default()' => false,
'rsssl_activation_required()' => false,
'rsssl_paid_only()' => false,
]
],
],
[
'id' => 'plesk_username',
'menu_id' => 'le-hosting',
'group_id' => 'le-hosting',
'type' => 'text',
'default' => '',
'label' => __( "Plesk username", 'really-simple-ssl' ),
'help' => [
'label' => 'default',
'title' => __( "Plesk username and password", "really-simple-ssl" ),
'text' => sprintf( __( "You can find your Plesk username and password in %s", 'really-simple-ssl' ), 'https://{your-plesk-host-name}:8443/smb/my-profile' ),
],
'required' => false,
'disabled' => false,
'server_conditions' => [
'relation' => 'AND',
[
'rsssl_is_plesk()' => true,
'rsssl_activated_by_default()' => false,
'rsssl_activation_required()' => false,
'rsssl_paid_only()' => false,
]
],
],
[
'id' => 'plesk_password',
'menu_id' => 'le-hosting',
'group_id' => 'le-hosting',
'type' => 'password',
'default' => '',
'label' => __( "Plesk password", 'really-simple-ssl' ),
'required' => false,
'disabled' => false,
'server_conditions' => [
'relation' => 'AND',
[
'rsssl_is_plesk()' => true,
'rsssl_activated_by_default()' => false,
'rsssl_activation_required()' => false,
'rsssl_paid_only()' => false,
]
],
],
[
'id' => 'store_credentials',
'menu_id' => 'le-hosting',
'group_id' => 'le-hosting',
'type' => 'checkbox',
'default' => '',
'label' => __( "Do you want to store these credentials for renewal purposes?", 'really-simple-ssl' ),
'help' => [
'label' => 'default',
'title' => 'Storing credentials',
'text' => __( "Store for renewal purposes. If not stored, renewal may need to be done manually.", 'really-simple-ssl' ),
],
'required' => false,
'disabled' => false,
'server_conditions' => [
'relation' => 'AND',
[
'rsssl_uses_known_dashboard()' => true,
'rsssl_activated_by_default()' => false,
'rsssl_activation_required()' => false,
'rsssl_paid_only()' => false,
]
],
],
[
'id' => 'directories',
'menu_id' => 'le-directories',
'group_id' => 'le-directories',
'condition_action' => 'hide',
'type' => 'letsencrypt',
'actions' => [
[
'description' => __( "Checking host...", "really-simple-ssl" ),
'action' => 'check_host',
'attempts' => 1,
'status' => 'inactive',
],
[
'description' => __( "Checking challenge directory...", "really-simple-ssl" ),
'action' => 'check_challenge_directory',
'attempts' => 1,
'status' => 'inactive',
],
[
'description' => __( "Checking key directory...", "really-simple-ssl" ),
'action' => 'check_key_directory',
'attempts' => 2,
'status' => 'inactive',
],
[
'description' => __( "Checking certs directory...", "really-simple-ssl" ),
'action' => 'check_certs_directory',
'attempts' => 1,
'status' => 'inactive',
],
[
'description' => __( "Checking permissions...", "really-simple-ssl" ),
'action' => 'check_writing_permissions',
'attempts' => 1,
'status' => 'inactive',
],
[
'description' => __( "Checking challenge directory reachable over http...", "really-simple-ssl" ),
'action' => 'challenge_directory_reachable',
'attempts' => 1,
'status' => 'inactive',
],
],
'react_conditions' => [
'relation' => 'AND',
[
'!verification_type' => 'dns',
]
],
],
[
'id' => 'dns-verification',
'menu_id' => 'le-dns-verification',
'group_id' => 'le-dns-verification',
'type' => 'letsencrypt',
'condition_action' => 'hide',
'actions' => [
[
'description' => __("Creating account...", "really-simple-ssl"),
'action'=> 'get_account',
'attempts' => 5,
'status' => 'inactive',
],
[
'description' => __("Retrieving DNS verification token...", "really-simple-ssl"),
'action'=> 'get_dns_token',
'attempts' => 5,
'status' => 'inactive',
],
],
'react_conditions' => [
'relation' => 'AND',
[
'verification_type' => 'dns',
]
],
],
[
'id' => 'generation',
'menu_id' => 'le-generation',
'group_id' => 'le-generation',
'type' => 'letsencrypt',
// 'server_conditions' => [
// 'relation' => 'AND',
// [
// 'rsssl_do_local_lets_encrypt_generation' => true,
// ]
// ],
'actions' => [
[
'description' => __("Checking if Terms & Conditions are accepted...", "really-simple-ssl"),
'action'=> 'terms_accepted',
'attempts' => 1,
'status' => 'inactive',
],
[
'description' => __("Creating account...", "really-simple-ssl"),
'action'=> 'get_account',
'attempts' => 5,
'status' => 'inactive',
],
[
'description' => __("Generating SSL certificate...", "really-simple-ssl"),
'action'=> 'create_bundle_or_renew',
'attempts' => 5,
'status' => 'inactive',
],
],
],
[
'id' => 'installation',
'menu_id' => 'le-installation',
'group_id' => 'le-installation',
'type' => 'letsencrypt',
'actions' => [
[
'description' => __("Searching for link to SSL installation page on your server...", "really-simple-ssl"),
'action'=> 'search_ssl_installation_url',
'attempts' => 1,
'status' => 'inactive',
],
],
],
[
'id' => 'activate_ssl',
'menu_id' => 'le-activate_ssl',
'group_id' => 'le-activate_ssl',
'type' => 'activate',
],
]);
if ( is_multisite() ) {
$index = array_search( 'system-status', array_column( $fields, 'id' ) );
$new_test = [
'description' => __( "Checking for subdomain setup...", "really-simple-ssl" ),
'action' => 'is_subdomain_setup',
'attempts' => 1,
'status' => 'inactive',
];
$current_tests = $fields[ $index ]['actions'];
$current_tests[] = $new_test;
$fields[ $index ]['actions'] = $current_tests;
}
return $fields;
}

View File

@@ -0,0 +1 @@
<?php // You don't belong here. ?>

View File

@@ -0,0 +1,181 @@
<?php
/**
* Show notice if certificate needs to be renewed.
*
* @param array $notices
*
* @return array
*/
function rsssl_le_get_notices_list($notices) {
//these notices are also loaded if Lets Encrypt is not loaded. To prevent errors, notices which require LE functionality are not loaded
if ( rsssl_generated_by_rsssl() && rsssl_letsencrypt_generation_allowed() ) {
//expiration date requests are cached.
$valid = RSSSL()->certificate->is_valid();
$certinfo = get_transient( 'rsssl_certinfo' );
$end_date = isset( $certinfo['validTo_time_t'] ) ? $certinfo['validTo_time_t'] : false;
//if the certificate expires within the grace period, allow renewal
//e.g. expiry date 30 may, now = 10 may => grace period 9 june.
$expiry_date = ! empty( $end_date ) ? date( get_option( 'date_format' ), $end_date ) : false;
if ( get_option( 'rsssl_create_folders_in_root' ) ) {
if ( ! get_option( 'rsssl_htaccess_file_set_key' ) || ! get_option( 'rsssl_htaccess_file_set_certs' ) || ! get_option( 'rsssl_htaccess_file_set_ssl' ) ) {
$notices['root_files_not_protected'] = array(
'condition' => array( 'rsssl_ssl_enabled' ),
'callback' => '_true_',
'score' => 10,
'output' => array(
'true' => array(
'msg' => __( "Your Key and Certificate directories are not properly protected.", "really-simple-ssl" ),
'url' => rsssl_link( "protect-ssl-generation-directories"),
'icon' => 'warning',
'plusone' => true,
'dismissible' => true,
),
),
);
}
}
if ( rsssl_letsencrypt_generation_allowed() ) {
if ( strpos(site_url(), 'www.') !== false ) {
$text = __( "The non-www version of your site does not point to this website. This is recommended, as it will allow you to add it to the certificate as well.", 'really-simple-ssl' );
} else {
$text = __( "The www version of your site does not point to this website. This is recommended, as it will allow you to add it to the certificate as well.", 'really-simple-ssl' );
}
$notices['alias_domain_notice'] = array(
'condition' => array( 'NOT rsssl_is_subdomain' ),
'callback' => 'RSSSL_LE()->letsencrypt_handler->alias_domain_available',
'score' => 10,
'output' => array(
'false' => array(
'title' => __( "Domain", 'really-simple-ssl' ),
'msg' => $text,
'icon' => 'open',
'plusone' => true,
'dismissible' => true,
),
),
'show_with_options' => [
'domain',
]
);
}
if ( $expiry_date ) {
$notices['ssl_detected'] = array(
'condition' => array( 'rsssl_ssl_enabled' ),
'callback' => 'RSSSL()->certificate->about_to_expire',
'score' => 10,
'output' => array(
'false' => array(
'msg' => sprintf( __( "Your certificate is valid until: %s", "really-simple-ssl" ), $expiry_date ),
'icon' => 'success'
),
'true' => array(
'msg' => sprintf( __( "Your certificate will expire on %s. You can renew it %shere%s.", "really-simple-ssl" ), $expiry_date, '<a href="' . rsssl_letsencrypt_wizard_url() . '">', '</a>' ),
'icon' => 'open',
'plusone' => true,
'dismissible' => false,
),
),
);
}
$notices['certificate_installation'] = array(
'condition' => array( 'rsssl_ssl_enabled', 'RSSSL()->certificate->about_to_expire' ),
'callback' => 'RSSSL_LE()->letsencrypt_handler->certificate_renewal_status_notice',
'score' => 10,
'output' => array(
'automatic-installation-failed' => array(
'msg' => sprintf( __( "The automatic installation of your certificate has failed. Please check your credentials, and retry the %sinstallation%s.",
"really-simple-ssl" ), '<a href="' . rsssl_letsencrypt_wizard_url() . '">', '</a>' ),
'icon' => 'open',
'plusone' => true,
'dismissible' => false,
),
'manual-installation' => array(
'msg' => sprintf( __( "The SSL certificate has been renewed, and requires manual %sinstallation%s in your hosting dashboard.", "really-simple-ssl" ),
'<a href="' . rsssl_letsencrypt_wizard_url('le-installation') . '">', '</a>' ),
'icon' => 'open',
'plusone' => true,
'dismissible' => false,
),
'manual-generation' => array(
'msg' => sprintf( __( "Automatic renewal of your certificate was not possible. The SSL certificate should be %srenewed%s manually.", "really-simple-ssl" ),
'<a href="' . rsssl_letsencrypt_wizard_url() . '">', '</a>' ),
'icon' => 'open',
'plusone' => true,
'dismissible' => false,
),
'automatic' => array(
'msg' => __( "Your certificate will be renewed and installed automatically.", "really-simple-ssl" ),
'icon' => 'open',
'plusone' => true,
'dismissible' => false,
),
),
);
}
//we run these notices only if the cert is generated by rsssl, or it's not valid.
if ( rsssl_generated_by_rsssl() || !RSSSL()->certificate->is_valid() ) {
$notices['can_use_shell'] = array(
'condition' => array('rsssl_can_install_shell_addon'),
'callback' => '_true_',
'score' => 10,
'output' => array(
'true' => array(
'msg' => __( "Your server provides shell functionality, which offers additional methods to install SSL. If installing SSL using the default methods is not possible, you can install the shell add on.", "really-simple-ssl" ),
'icon' => 'open',
'url' => "installing-ssl-using-shell-functions",
'plusone' => true,
'dismissible' => true,
),
),
);
//show notice if the shell exec add on is not up to date
if ( function_exists('rsssl_le_load_shell_addon') && defined('rsssl_shell_version') && version_compare(rsssl_shell_version,'2.0.0','<')){
$notices['old_shell_exec_plugin'] = array(
'callback' => '_true_',
'score' => 10,
'output' => array(
'true' => array(
'msg' => __( "You are using the Really Simple Security Shell Exec add on, but your current version needs to be updated.", "really-simple-ssl" ),
'icon' => 'warning',
'url' => "installing-ssl-using-shell-functions",
'plusone' => true,
'dismissible' => false,
),
),
);
}
}
return $notices;
}
add_filter( 'rsssl_notices', 'rsssl_le_get_notices_list', 30, 1 );
/**
* Replace the go pro or scan button with a renew SSL button when the cert should be renewed.
*/
function rsssl_le_progress_footer_renew_ssl($button){
if ( rsssl_ssl_enabled() && RSSSL()->certificate->about_to_expire() ){
$status = RSSSL_LE()->letsencrypt_handler->certificate_renewal_status_notice;
switch ($status){
case 'manual-installation':
$button_text = __("Renew installation", "really-simple-ssl");
break;
case 'manual-generation':
$button_text = __("Renew certificate", "really-simple-ssl");
break;
default:
$button_text = __("Renew certificate", "really-simple-ssl");//false;
}
if ($button_text) {
$url = rsssl_letsencrypt_wizard_url();
$button = '<a href="'.$url.'" class="button button-default">'.$button_text.'</a>';
}
}
return $button;
}
add_filter("rsssl_progress_footer_right", "rsssl_le_progress_footer_renew_ssl", 30);

View File

@@ -0,0 +1,59 @@
<?php
defined( 'ABSPATH' ) or die();
add_action( 'plugins_loaded', 'rsssl_le_schedule_cron' );
function rsssl_le_schedule_cron() {
//only run if SSL is enabled.
if ( !rsssl_get_option('ssl_enabled') ) {
return;
}
//only if generated by RSSSL.
if ( ! get_option( 'rsssl_le_certificate_generated_by_rsssl' ) ) {
return;
}
add_action( 'rsssl_week_cron', 'rsssl_le_cron_maybe_start_renewal' );
add_action( 'rsssl_daily_cron', 'rsssl_le_check_renewal_status' );
}
/**
* Check if the certificate is generated by RSSSL. If so, renew if necessary
*/
function rsssl_le_cron_maybe_start_renewal(){
if ( !rsssl_generated_by_rsssl() ) {
return;
}
if ( RSSSL_LE()->letsencrypt_handler->cron_certificate_needs_renewal() ) {
update_option("rsssl_le_start_renewal", true, false);
}
if ( RSSSL_LE()->letsencrypt_handler->certificate_install_required() ) {
update_option("rsssl_le_start_installation", true, false);
}
}
function rsssl_le_check_renewal_status(){
if ( !rsssl_generated_by_rsssl() ) {
return;
}
//when DNS validated, without api, we cannot autorenew
if ( !RSSSL_LE()->letsencrypt_handler->ssl_generation_can_auto_renew() ) {
return;
}
$renewal_active = get_option("rsssl_le_start_renewal");
$installation_active = get_option("rsssl_le_start_installation");
if ( $renewal_active ) {
RSSSL_LE()->letsencrypt_handler->create_bundle_or_renew();
} else if ( $installation_active ) {
RSSSL_LE()->letsencrypt_handler->cron_renew_installation();
}
}

View File

@@ -0,0 +1,109 @@
<?php
# No need for the template engine
define( 'WP_USE_THEMES', false );
#find the base path
define( 'BASE_PATH', rsssl_find_wordpress_base_path()."/" );
# Load WordPress Core
if ( !file_exists(BASE_PATH . 'wp-load.php') ) {
die("WordPress not installed here");
}
require_once( BASE_PATH.'wp-load.php' );
require_once( ABSPATH.'wp-includes/class-phpass.php' );
require_once( ABSPATH . 'wp-admin/includes/image.php' );
if ( !rsssl_user_can_manage() ) {
die();
}
if ( !isset($_GET["type"]) ) {
die();
}
if (!isset($_GET['token'])) {
die();
}
if (!wp_verify_nonce($_GET['token'], 'rsssl_download_cert')){
die();
}
$type = sanitize_title($_GET['type']);
switch($type) {
case 'certificate':
$file = get_option('rsssl_certificate_path');
$file_name = 'certificate.cert';
break;
case 'private_key':
$file = get_option('rsssl_private_key_path');
$file_name = 'private.pem';
break;
case 'intermediate':
$file = get_option('rsssl_intermediate_path');
$file_name = 'intermediate.pem';
break;
default:
$file = false;
}
if (!file_exists($file)) {
$content = __("File missing. Please retry the previous steps.", "really-simple-ssl");
die();
} else {
$content = file_get_contents($file);
}
$fp = fopen($file, 'rb');
if ($fp) {
if (function_exists('mb_strlen')) {
$fsize = mb_strlen($content, '8bit');
} else {
$fsize = strlen($content);
}
$path_parts = pathinfo($file);
header("Content-type: text/plain");
header("Content-Disposition: attachment; filename=\"".$file_name."\"");
header("Content-length: $fsize");
header("Cache-Control: private",false); // required for certain browsers
header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Content-Transfer-Encoding: binary");
echo $content;
} else {
echo "Something went wrong #2";
}
fclose($fp);
function rsssl_find_wordpress_base_path()
{
$path = dirname(__FILE__);
do {
if (file_exists($path . "/wp-config.php")) {
//check if the wp-load.php file exists here. If not, we assume it's in a subdir.
if ( file_exists( $path . '/wp-load.php') ) {
return $path;
} else {
//wp not in this directory. Look in each folder to see if it's there.
if ( file_exists( $path ) && $handle = opendir( $path ) ) {
while ( false !== ( $file = readdir( $handle ) ) ) {
if ( $file != "." && $file != ".." ) {
$file = $path .'/' . $file;
if ( is_dir( $file ) && file_exists( $file . '/wp-load.php') ) {
$path = $file;
break;
}
}
}
closedir( $handle );
}
}
return $path;
}
} while ($path = realpath("$path/.."));
return false;
}

View File

@@ -0,0 +1,591 @@
<?php defined( 'ABSPATH' ) or die();
/**
* Create a generic read more text with link for help texts.
*
* @param string $url
* @param bool $add_space
*
* @return string
*/
function rsssl_le_read_more( $url, $add_character = ' ' ) {
$html = sprintf( __( "For more information, please read this %sarticle%s",
'really-simple-ssl' ), '<a target="_blank" rel="noopener noreferrer" href="' . $url . '">',
'</a>' );
if ( is_string($add_character) ) {
$html = $add_character . $html;
}
return $html;
}
/**
* Check if we need to use DNS verification
* @return bool
*/
function rsssl_dns_verification_required(){
if ( get_option('rsssl_manually_changed_verification_type') ) {
return rsssl_get_option( 'verification_type' ) === 'dns';
}
/**
* If our current hosting provider does not allow or require local SSL certificate generation,
* We do not need to DNS verification either.
*/
if ( !rsssl_do_local_lets_encrypt_generation() ) {
return false;
}
if ( rsssl_get_option('verification_type')==='dns' ) {
return true;
}
if ( rsssl_wildcard_certificate_required() ) {
//if the user hasn't manually forced the verification type to anything else, we set it to dns now.
//otherwise we get a difference between this requirement, and the actual verification type that could be 'dir'
if ( !get_option('rsssl_manually_changed_verification_type') && rsssl_get_option('verification_type')!=='dns' ) {
rsssl_update_option('verification_type', 'dns');
}
return true;
}
return false;
}
if ( !function_exists('rsssl_is_cpanel')) {
/**
* Check if we're on CPanel
*
* @return bool
*/
function rsssl_is_cpanel() {
$open_basedir = ini_get("open_basedir");
if ( empty($open_basedir) && file_exists( "/usr/local/cpanel" ) ) {
return true;
} else if (rsssl_check_port(2082)) {
return true;
} else {
return false;
}
}
}
if (!function_exists('rsssl_cpanel_api_supported')) {
/**
* Check if CPanel supports the api
*
* @return bool
*/
function rsssl_cpanel_api_supported() {
if ( rsssl_is_cpanel() ) {
return true;
}
return !rsssl_openbasedir_restriction("/usr/local/cpanel/php/cpanel.php") && file_exists( "/usr/local/cpanel/php/cpanel.php" );
}
}
if (!function_exists('rsssl_activated_by_default')) {
/**
* Check if the host has ssl, activated by default
*
* @return bool
*/
function rsssl_activated_by_default() {
$activated_by_default = false;
$activated_by_default_hosts = RSSSL_LE()->hosts->activated_by_default;
$current_host = rsssl_get_other_host();
if ( in_array( $current_host, $activated_by_default_hosts ) ) {
$activated_by_default = true;
}
return $activated_by_default;
}
}
if ( !function_exists('rsssl_openbasedir_restriction')) {
function rsssl_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';
}
}
if (!function_exists('rsssl_activation_required')) {
/**
* Check if the host has ssl, activation required
*
* @return bool
*/
function rsssl_activation_required() {
$dashboard_activation_required = false;
$dashboard_activation_required_hosts = RSSSL_LE()->hosts->dashboard_activation_required;
$current_host = rsssl_get_other_host();
if ( in_array( $current_host, $dashboard_activation_required_hosts ) ) {
$dashboard_activation_required = true;
}
return $dashboard_activation_required;
}
}
if (!function_exists('rsssl_paid_only')) {
/**
* Check if the host has ssl, paid only
*
* @return bool
*/
function rsssl_paid_only() {
$paid_only = false;
$paid_only_hosts = RSSSL_LE()->hosts->paid_only;
$current_host = rsssl_get_other_host();
if ( in_array( $current_host, $paid_only_hosts ) ) {
$paid_only = true;
}
return $paid_only;
}
}
if ( !function_exists('rsssl_is_plesk')) {
/**
* https://stackoverflow.com/questions/26927248/how-to-detect-servers-control-panel-type-with-php
* @return false
*/
function rsssl_is_plesk() {
if ( get_option('rsssl_hosting_dashboard')==='plesk' ){
return true;
}
//cpanel takes precedence, as it's more precise
if ( rsssl_is_cpanel() ) {
return false;
}
$open_basedir = ini_get("open_basedir");
if ( empty($open_basedir) && is_dir( '/usr/local/psa' ) ) {
return true;
} else if (rsssl_check_port(8443)) {
return true;
} else {
return false;
}
}
}
if ( !function_exists('rsssl_is_directadmin')) {
/**
* https://stackoverflow.com/questions/26927248/how-to-detect-servers-control-panel-type-with-php
* @return bool
*/
function rsssl_is_directadmin() {
if ( get_option('rsssl_hosting_dashboard')==='directadmin' ){
return true;
}
//cpanel takes precedence, as it's more precise
if ( rsssl_is_cpanel() ) {
return false;
}
if ( rsssl_is_plesk() ) {
return false;
}
if (rsssl_check_port(2222)) {
return true;
} else {
return false;
}
}
}
/**
* @param int $port
*
* @return bool
*/
function rsssl_check_port( $port)
{
$port_check_status = get_option("rsssl_port_check_$port");
if ( !function_exists('fsockopen') || $port_check_status === 'fail' ) {
return false;
}
$ipAddress = gethostbyname('localhost');
$link = @fsockopen( $ipAddress, $port, $errno, $error, 5 );
if ( $link ) {
update_option("rsssl_port_check_$port", 'success', false);
return true;
}
update_option("rsssl_port_check_$port", 'fail', false);
return false;
}
if ( !function_exists('rsssl_get_other_host') ) {
/**
* Get the selected hosting provider, if any.
* @return bool|string
*/
function rsssl_get_other_host() {
return rsssl_get_option( 'other_host_type', false );
}
}
/**
* Add some information to the javascript
* @param array $args
*
* @return array
*/
function rsssl_le_localize_script($args){
$hosting_dashboard = 'other';
if ( rsssl_is_cpanel() ) $hosting_dashboard = 'cpanel';
if ( rsssl_is_directadmin() ) $hosting_dashboard = 'directadmin';
if ( rsssl_is_plesk() ) $hosting_dashboard = 'plesk';
$args['hosting_dashboard'] = $hosting_dashboard;
return $args;
}
add_filter("rsssl_localize_script", 'rsssl_le_localize_script', 10, 3);
if ( !function_exists('rsssl_progress_add')) {
/**
* @param string $item
*/
function rsssl_progress_add( $item ) {
$progress = get_option( "rsssl_le_installation_progress", array() );
if ( ! in_array( $item, $progress ) ) {
$progress[] = $item;
update_option( "rsssl_le_installation_progress", $progress, false );
}
}
}
if ( !function_exists('rsssl_uses_known_dashboard')) {
/**
* Check if website uses any of the known dashboards.
*/
function rsssl_uses_known_dashboard( ) {
if ( rsssl_is_cpanel() || rsssl_is_plesk() || rsssl_is_directadmin() ) {
return true;
} else {
return false;
}
}
}
if ( !function_exists('rsssl_is_ready_for')) {
/**
* @param string $item
*/
function rsssl_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;
} else{
return false;
}
}
}
function rsssl_get_not_completed_steps($item){
$sequence = array_column( rsssl_le_steps(), 'id');
//drop first
array_shift($sequence);
//drop all statuses after $item. We only need to know if all previous ones have been completed
$index = array_search($item, $sequence);
$sequence = array_slice($sequence, 0, $index, true);
$not_completed = array();
$finished = get_option("rsssl_le_installation_progress", array());
foreach ($sequence as $status ) {
if (!in_array($status, $finished)) {
$not_completed[] = $status;
}
}
return $not_completed;
}
if ( !function_exists('rsssl_progress_remove')) {
/**
* @param string $item
*/
function rsssl_progress_remove( $item ) {
$progress = get_option( "rsssl_le_installation_progress", array() );
if ( in_array( $item, $progress ) ) {
$index = array_search( $item, $progress );
unset( $progress[ $index ] );
update_option( "rsssl_le_installation_progress", $progress, false );
}
}
}
if ( !function_exists('rsssl_do_local_lets_encrypt_generation')) {
/**
* Check if the setup requires local certificate generation
* @return bool
*/
function rsssl_do_local_lets_encrypt_generation() {
$not_local_cert_hosts = RSSSL_LE()->hosts->not_local_certificate_hosts;
$current_host = rsssl_get_other_host();
if ( in_array( $current_host, $not_local_cert_hosts ) ) {
return false;
}
return true;
}
}
if ( !function_exists('rsssl_get_manual_instructions_text')) {
/**
* Manual installation instructions
*
* @param string $url
*
* @return string
*/
function rsssl_get_manual_instructions_text( $url ) {
$default_url = rsssl_link('install-ssl-certificate');
$dashboard_activation_required = rsssl_activation_required();
$activated_by_default = rsssl_activated_by_default();
$paid_only = rsssl_paid_only();
$button_activate = '<br><a href="' . $default_url . '" target="_blank" rel="noopener noreferrer" class="button button-primary">' . __( "Instructions", "really-simple-ssl" ) . '</a>&nbsp;&nbsp;';
$button_complete = '<br><a href="' . $default_url . '" target="_blank" rel="noopener noreferrer" class="button button-primary">' . __( "Instructions", "really-simple-ssl" ) . '</a>&nbsp;&nbsp;';
if ( $url === $default_url ) {
$complete_manually = sprintf( __( "Please complete manually in your hosting dashboard.", "really-simple-ssl" ), '<a target="_blank" href="' . $url . '">', '</a>' );
$activate_manually = sprintf( __( "Please activate it manually on your hosting dashboard.", "really-simple-ssl" ), '<a target="_blank" href="' . $url . '">', '</a>' );
} else {
$complete_manually = sprintf( __( "Please complete %smanually%s", "really-simple-ssl" ), '<a target="_blank" rel="noopener noreferrer" href="' . $url . '">', '</a>' );
$activate_manually = sprintf( __( "Please activate it on your dashboard %smanually%s", "really-simple-ssl" ), '<a target="_blank" rel="noopener noreferrer" href="' . $url . '">', '</a>' );
$button_activate .= '<a href="' . $url . '" target="_blank" rel="noopener noreferrer" class="button button-primary">' . __( "Go to activation", "really-simple-ssl" ) . '</a>';
$button_complete .= '<a href="' . $url . '" target="_blank" rel="noopener noreferrer" class="button button-primary">' . __( "Go to installation", "really-simple-ssl" ) . '</a>';
}
if ( $activated_by_default ) {
$msg
= 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 "rel="noopener noreferrer" href="' . $url . '">', '</a>' ) . '&nbsp' .
__( "After completing the installation, you can continue to the next step to complete your configuration.", "really-simple-ssl" );
} else if ( $dashboard_activation_required ) {
$msg = __( "You already have free SSL on your hosting environment.", "really-simple-ssl" ) . '&nbsp' .
$activate_manually . ' ' .
__( "After completing the installation, you can continue to the next step to complete your configuration.", "really-simple-ssl" )
. $button_activate;
} else if ( $paid_only ) {
$msg
= 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" rel="noopener noreferrer" href='.rsssl_link("hosting-providers-with-free-ssl").'>', '</a>' );
} else {
$msg = __( "Your hosting environment does not allow automatic SSL installation.", "really-simple-ssl" ) . ' ' .
$complete_manually . ' ' .
sprintf( __( "You can follow these %sinstructions%s.", "really-simple-ssl" ), '<a target="_blank" rel="noopener noreferrer" href="' . $default_url . '">', '</a>' ) . '&nbsp' .
__( "After completing the installation, you can continue to the next step to complete your configuration.", "really-simple-ssl" )
. $button_complete;
}
return $msg;
}
}
register_activation_hook( __FILE__, 'rsssl_set_activation_time_stamp' );
if ( ! function_exists( 'rsssl_set_activation_time_stamp' ) ) {
function rsssl_set_activation_time_stamp( $networkwide ) {
update_option( 'rsssl_activation_time', time(), false );
}
}
if ( ! function_exists( 'rsssl_array_filter_multidimensional' ) ) {
function rsssl_array_filter_multidimensional(
$array, $filter_key, $filter_value
) {
$new = array_filter( $array,
function ( $var ) use ( $filter_value, $filter_key ) {
return isset( $var[ $filter_key ] ) ? ( $var[ $filter_key ]
== $filter_value )
: false;
} );
return $new;
}
}
if ( !function_exists('rsssl_is_subdomain') ) {
/**
* Check if we're on a subdomain.
* If this is a www domain, we return false
*/
function rsssl_is_subdomain(){
$domain = rsssl_get_domain();
if ( strpos($domain, 'www.') !== false ) return false;
$root = rsssl_get_root_domain($domain);
if ($root === $domain ) {
return false;
} else {
return true;
}
}
}
if ( !function_exists('rsssl_get_root_domain') ) {
/**
* Get root domain of a domain
*/
function rsssl_get_root_domain($domain){
$sub = strtolower(trim($domain));
$count = substr_count($sub, '.');
if($count === 2){
if(strlen(explode('.', $sub)[1]) > 3) $sub = explode('.', $sub, 2)[1];
} else if($count > 2){
$sub = rsssl_get_root_domain(explode('.', $sub, 2)[1]);
}
return $sub;
}
}
if ( ! function_exists( 'rsssl_get_domain' ) ) {
/**
* Get current domain
*
* @return string
*/
function rsssl_get_domain() {
//Get current domain
$domain = site_url();
//Parse to strip off any /subfolder/
$parse = parse_url($domain);
$domain = $parse['host'];
$domain = str_replace(array('http://', 'https://' ), '', $domain);
return $domain;
}
}
function rsssl_insert_after_key($array, $key, $items){
$keys = array_keys($array);
$key = array_search($key, $keys);
$array = array_slice($array, 0, $key, true) +
$items +
array_slice($array, 3, count($array)-3, true);
return $array;
}
if ( !function_exists('rsssl_wildcard_certificate_required') ) {
/**
* Check if the site requires a wildcard
*
* @return bool
*/
function rsssl_wildcard_certificate_required() {
//if DNS verification, create wildcard.
if ( rsssl_get_option('verification_type') === 'dns' ) {
return true;
}
if ( ! is_multisite() ) {
return false;
} else {
if ( defined( 'SUBDOMAIN_INSTALL' ) && SUBDOMAIN_INSTALL ) {
return true;
} else {
return false;
}
}
}
}
if ( !function_exists('rsssl_can_install_shell_addon') ) {
/**
* check if this environment has shell capability
*
* @return bool
*/
function rsssl_can_install_shell_addon(){
//if not cpanel
if ( !rsssl_is_cpanel() ) {
return false;
}
//if already installed
if (defined('rsssl_shell_path')){
return false;
}
if ( function_exists('shell_exec') || function_exists('system') || function_exists('passthru') || function_exists('exec') ) {
return true;
} else {
return false;
}
}
}
if ( !function_exists('rsssl_generated_by_rsssl')) {
/**
* If a bundle generation is completed, this value is set to true.
*
* @return bool
*/
function rsssl_generated_by_rsssl() {
return get_option( 'rsssl_le_certificate_generated_by_rsssl' );
}
}
/**
* Checks if a CAA DNS record is preventing the use of Let's Encrypt for the current site.
*
* @return bool Returns true if a CAA DNS record exists and does not allow Let's Encrypt, false otherwise.
*/
if ( ! function_exists( 'rsssl_caa_record_prevents_le' ) ) {
function rsssl_caa_record_prevents_le(): bool {
// Get DNS CAA records for site_url()
$caa_records = dns_get_record( parse_url( site_url(), PHP_URL_HOST ), DNS_CAA );
// If no CAA records found, return false
if ( empty( $caa_records ) ) {
return false;
}
// Check if the CAA record contains letsencrypt.org
$caa_contains_le = false;
foreach ( $caa_records as $caa_record ) {
if ( strpos( $caa_record['value'], 'letsencrypt.org' ) !== false ) {
$caa_contains_le = true;
break;
}
}
// If the CAA record is set, but does not contain letsencrypt.org, return true;
if ( ! $caa_contains_le ) {
return true;
}
// CAA record contains Let's Encrypt, generation allowed
return false;
}
}

View File

@@ -0,0 +1 @@
<?php // You don't belong here. ?>

View File

@@ -0,0 +1,259 @@
<?php
/**
* @package CloudWays
* @author Rogier Lankhorst
* @copyright Copyright (C) 2021, Rogier Lankhorst
* @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3
* @link https://really-simple-ssl.com
* @since Class available since Release 5.0.0
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
require_once rsssl_path . 'lib/admin/class-encryption.php';
use RSSSL\lib\admin\Encryption;
class rsssl_Cloudways {
use Encryption;
private $email;
private $api_key;
public $ssl_installation_url;
/**
* Initiates the cloudways class.
*
* @param string $email
* @param string $api_key
*/
public function __construct( ) {
$this->email = rsssl_get_option('cloudways_user_email');
$this->api_key = $this->decrypt_if_prefixed( rsssl_get_option('cloudways_api_key') );
$this->ssl_installation_url = "";
}
/**
*
* @param string $method GET|POST|PUT|DELETE
* @param string $url relative URL for the call
* @param string $accessToken Access token generated using OAuth Call
* @param array $post Optional post data for the call
*
* @return RSSSL_RESPONSE
*/
private function callCloudwaysAPI( $method, $url, $accessToken, $post = [] ) {
$baseURL = 'https://api.cloudways.com/api/v1/';
try {
$ch = curl_init();
curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, $method );
curl_setopt( $ch, CURLOPT_URL, $baseURL . $url );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
if ( $accessToken ) {
curl_setopt( $ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . $accessToken ] );
}
//ssl_domains[]=fungibleownership.com&ssl_domains[]=www.fungibleownership.com
$encoded = '';
if ( count( $post ) ) {
foreach ( $post as $name => $value ) {
if ( is_array( $value) ) {
foreach ( $value as $sub_value ) {
$encoded .= $name.'[]='.urlencode( $sub_value) . '&';
}
} else {
$encoded .= urlencode( $name ) . '=' . urlencode( $value ) . '&';
}
}
$encoded = substr( $encoded, 0, strlen( $encoded ) - 1 );
curl_setopt( $ch, CURLOPT_POSTFIELDS, $encoded );
curl_setopt( $ch, CURLOPT_POST, 1 );
}
$output = curl_exec( $ch );
$httpcode = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
if ($output && isset($output->error_description)) {
return new RSSSL_RESPONSE( 'error', 'stop', $output->error_description, false );
} else if ($httpcode != '200' && $output && isset($output->message) ){
return new RSSSL_RESPONSE( 'error', 'stop', $output->message );
} else if ( $httpcode != '200' ) {
$message = $httpcode . ' output: ' . substr( $output, 0, 10000 );
return new RSSSL_RESPONSE( 'error', 'stop', $message );
}
curl_close( $ch );
return new RSSSL_RESPONSE( 'success', 'continue', '', json_decode( $output ) );
} catch(Exception $e) {
return new RSSSL_RESPONSE( 'error', 'stop', $e->getMessage() );
}
}
/**
* Get an access token
* @return RSSSL_RESPONSE
*/
private function getAccessToken() {
$accessToken = get_transient('rsssl_cw_t');
if (!$accessToken) {
$response = $this->callCloudwaysAPI( 'POST', '/oauth/access_token', null, [ 'email' => $this->email, 'api_key' => $this->api_key ] );
if ($response->status === 'success' ) {
$accessToken = $response->output->access_token;
set_transient('rsssl_cw_t', $accessToken, 1800);
} else {
return new RSSSL_RESPONSE( 'error', 'stop', $response->message );
}
}
return new RSSSL_RESPONSE( 'success', 'continue','', $accessToken );
}
/**
* @param array $domains
*
* @return RSSSL_RESPONSE
*/
public function installSSL($domains){
$response = $this->getAccessToken();
if ( $response->status !== 'success' ) {
return new RSSSL_RESPONSE('error','stop',$response->message);
}
$accessToken = $response->output;
$response = $this->getServerInfo();
if ($response->status === 'success' ) {
$server_id = get_transient('rsssl_cw_server_id' );
$app_id = get_transient('rsssl_cw_app_id');
$args = [
'server_id' => $server_id,
'app_id' => $app_id,
'ssl_email' => $this->email,
'wild_card' => RSSSL_LE()->letsencrypt_handler->is_wildcard(),
'ssl_domains' => $domains,
];
$response = $this->callCloudWaysAPI( 'POST', 'security/lets_encrypt_install', $accessToken, $args );
}
return $response;
}
/**
*
* @return RSSSL_RESPONSE
*/
public function enableAutoRenew(){
$response = $this->getAccessToken();
if ( $response->status !== 'success' ) {
return new RSSSL_RESPONSE('error','stop', __("Failed retrieving access token","really-simple-ssl"));
}
$accessToken = $response->output;
$response = $this->getServerInfo();
if ($response->status === 'success' ) {
$app_id = get_transient('rsssl_cw_app_id');
$server_id = get_transient('rsssl_cw_server_id' );
$response = $this->callCloudWaysAPI( 'POST', 'security/lets_encrypt_auto', $accessToken,
[
'server_id' => $server_id,
'app_id' => $app_id,
'auto' => true,
]
);
}
if ( $response->status === 'success' ) {
$status = 'success';
$action = 'continue';
$message = __("Successfully installed Let's Encrypt","really-simple-ssl");
} elseif ($response->status === 'error') {
//in some cases, the process is already started, which also signifies success.
if ( strpos($response->message, 'An operation is already in progress for this server')) {
$status = 'success';
$action = 'continue';
$message = __("Successfully installed Let's Encrypt","really-simple-ssl");
} else {
$status = $response->status;
$action = $response->action;
$message = $response->message;
}
} else {
$status = $response->status;
$action = $response->action;
$message = __("Error enabling auto renew for Let's Encrypt","really-simple-ssl");
}
return new RSSSL_RESPONSE( $status, $action, $message );
}
/**
* Get the server id and app id
*
* @return RSSSL_RESPONSE
*/
public function getServerInfo(){
if ( get_transient('rsssl_cw_app_id') && get_transient('rsssl_cw_server_id' ) ) {
$status = 'success';
$action = 'continue';
$message = __("Successfully retrieved server id and app id","really-simple-ssl");
return new RSSSL_RESPONSE( $status, $action, $message );
}
$response = $this->getAccessToken();
if ( $response->status !== 'success' ) {
return new RSSSL_RESPONSE('error','stop', $response->message);
}
$accessToken = $response->output;
$response = $this->callCloudwaysAPI('GET', '/server', $accessToken );
$success = false;
if ($response->status === 'success') {
$serverList = $response->output;
$servers = $serverList->servers;
foreach ($servers as $server ){
$apps = $server->apps;
foreach ($apps as $app ){
$app_domain = $app->cname;
$this_site_domain = str_replace(array('https://', 'http://', 'www.'), '',site_url());
if (strpos($app_domain, $this_site_domain) !== false ) {
$success = true;
set_transient('rsssl_cw_app_id', $app->id, WEEK_IN_SECONDS);
set_transient('rsssl_cw_server_id', $server->id, WEEK_IN_SECONDS);
break 2;
}
}
}
}
if ( $success ) {
$status = 'success';
$action = 'continue';
$message = __("Successfully retrieved server id and app id","really-simple-ssl");
} else {
$status = 'error';
$action = 'stop';
if ( isset($serverList->error_description) ) {
$message = $serverList->error_description;
} else {
$message = __("Could not retrieve server list","really-simple-ssl");
}
}
return new RSSSL_RESPONSE( $status, $action, $message );
}
}

View File

@@ -0,0 +1,62 @@
<?php defined( 'ABSPATH' ) or die();
function rsssl_cloudways_server_data(){
require_once( rsssl_le_path . 'integrations/cloudways/cloudways.php' );
$cloudways = new rsssl_Cloudways();
return $cloudways->getServerInfo();
}
function rsssl_cloudways_install_ssl(){
if (rsssl_is_ready_for('installation')) {
require_once( rsssl_le_path . 'integrations/cloudways/cloudways.php' );
$domains = RSSSL_LE()->letsencrypt_handler->get_subjects();
$cloudways = new rsssl_Cloudways();
$response = $cloudways->installSSL($domains);
if ($response->status === 'success') {
update_option('rsssl_le_certificate_installed_by_rsssl', 'cloudways', false);
}
return $response;
} 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);
}
}
function rsssl_cloudways_auto_renew(){
require_once( rsssl_le_path . 'integrations/cloudways/cloudways.php' );
$cloudways = new rsssl_Cloudways();
return $cloudways->enableAutoRenew();
}
function rsssl_cloudways_add_condition_actions($fields){
$index = array_search('installation',array_column($fields,'id'));
$fields[$index]['actions'] = array(
array(
'description' => __("Retrieving Cloudways server data...", "really-simple-ssl"),
'action'=> 'rsssl_cloudways_server_data',
'attempts' => 5,
'status' => 'inactive',
),
array(
'description' => __("Installing SSL certificate...", "really-simple-ssl"),
'action'=> 'rsssl_cloudways_install_ssl',
'attempts' => 5,
'status' => 'inactive',
),
array(
'description' => __("Enabling auto renew...", "really-simple-ssl"),
'action'=> 'rsssl_cloudways_auto_renew',
'attempts' => 5,
'status' => 'inactive',
),
);
//drop store credentials field
$creds_index = array_search('store_credentials',array_column($fields,'id'));
unset($fields[$creds_index]);
return $fields;
}
add_filter( 'rsssl_fields', 'rsssl_cloudways_add_condition_actions' );

View File

@@ -0,0 +1,336 @@
<?php
defined( 'ABSPATH' ) or die();
require_once rsssl_path . 'lib/admin/class-encryption.php';
use RSSSL\lib\admin\Encryption;
require_once( rsssl_le_path . 'integrations/cpanel/functions.php' );
/**
* Completely rebuilt and improved on the FreeSSL.tech Auto CPanel class by Anindya Sundar Mandal
*/
class rsssl_cPanel
{
use Encryption;
public $host;
private $username;
private $password;
public $ssl_installation_url;
/**
* Initiates the cPanel class.
*/
public function __construct()
{
$username = rsssl_get_option('cpanel_username');
$password = $this->decrypt_if_prefixed( rsssl_get_option('cpanel_password') );
$host = rsssl_get_option('cpanel_host');
$this->host = str_replace( array('http://', 'https://', ':2083',':'), '', $host );
$this->username = $username;
$this->password = $password;
$this->ssl_installation_url = 'https://'.$this->host.":2083/frontend/jupiter/ssl/install.html";
}
/**
* Check if all creds are available
* @return bool
*/
public function credentials_available(){
if (!empty($this->host) && !empty($this->password) && !empty($this->username)) {
return true;
}
return false;
}
/**
* Install SSL for all passed domains
* @param array $domains
*
* @return RSSSL_RESPONSE
*/
public function installSSL($domains) {
$response = false;
if ( is_array($domains) && count($domains)>0 ) {
foreach( $domains as $domain ) {
$response_item = $this->installSSLPerDomain($domain);
//set on first iteration
if ( !$response ) {
$response = $response_item;
}
//override if not successfull, to always get the error.
if ( $response->status !== 'success' ) {
$response = $response_item;
}
}
}
if ( !$response ) {
$response = new RSSSL_RESPONSE('error', 'stop', __("No valid list of domains.", "really-simple-ssl"));
}
if ( $response->status === 'success' ) {
update_option('rsssl_le_certificate_installed_by_rsssl', 'cpanel:default', false);
}
return $response;
}
/**
* Install an SSL certificate on the domain provided - using cPanel UAPI.
*
* @param string $domain
*
* @return RSSSL_RESPONSE
*/
public function installSSLPerDomain($domain)
{
$shell_addon_active = defined('rsssl_shell_path');
$key_file = get_option('rsssl_private_key_path');
$cert_file = get_option('rsssl_certificate_path');
$cabundle_file = get_option('rsssl_intermediate_path');
$request_uri = 'https://'.$this->host.':2083/execute/SSL/install_ssl';
$payload = [
'domain' => $domain,
'cert' => file_get_contents($cert_file),
'key' => file_get_contents($key_file),
'cabundle' => file_get_contents($cabundle_file),
];
$response_raw = $this->connectUapi($request_uri, $payload);
$isIpBlock = $this->isIpBlock($response_raw);
$isLoginError = !$isIpBlock && $this->isLoginError($response_raw);
$response = json_decode($response_raw);
//Validate $response
if ($isIpBlock) {
update_option( 'rsssl_installation_error', 'cpanel:autossl', false );
$status = 'error';
$action = 'stop';
$message = __( "Your website's ip address is blocked. Please add your domain's ip address to the security policy in CPanel", "really-simple-ssl" );
} else if ($isLoginError) {
update_option('rsssl_installation_error', 'cpanel:autossl', false);
$status = 'error';
$action = 'stop';
$message = __("Login credentials incorrect. Please check your login credentials for cPanel.","really-simple-ssl");
} else if ( empty($response) ) {
update_option('rsssl_installation_error', 'cpanel:default', false);
$status = 'warning';
$action = $shell_addon_active ? 'skip' : 'continue';
$message = rsssl_get_manual_instructions_text($this->ssl_installation_url);
} else if ($response->status) {
delete_option('rsssl_installation_error' );
$status = 'success';
$action = 'continue';
$message = sprintf(__("SSL successfully installed on %s","really-simple-ssl"), $domain);
} else {
update_option('rsssl_installation_error', 'cpanel:default', false);
$status = 'error';
$action = $shell_addon_active ? 'skip' : 'continue';
$message = __("Errors were reported during installation.","really-simple-ssl").'<br> '.$response->errors[0];
}
return new RSSSL_RESPONSE($status, $action, $message);
}
/**
* Based on the known output of an ip block html page, check if the user should whitelist their own website ip.
* @param $raw
*
* @return bool
*/
public function isIpBlock($raw){
$triggers = [
'security_policy',
'You appear to be logging in from an unknown location',
'unrecognized IP address',
];
foreach($triggers as $key => $trigger ) {
if (strpos($raw,$trigger)!==false) {
return true;
}
}
return false;
}
/**
* Based on the known output of an ip block html page, check if the user has entered incorrect login creds
* @param $raw
*
* @return bool
*/
public function isLoginError($raw){
$triggers = [
'input-field-login icon password',
'name="pass" id="pass"',
];
foreach($triggers as $key => $trigger ) {
if (strpos($raw,$trigger)!==false) {
return true;
}
}
return false;
}
/**
* @param $domains
*
* @return RSSSL_RESPONSE
*/
public function enableAutoSSL($domains){
$domains = implode(',', $domains);
$request_uri = 'https://'.$this->host.':2083/execute/SSL/remove_autossl_excluded_domains';
$payload = [
'domains' => $domains,
];
$response_raw = $this->connectUapi($request_uri, $payload);
$isIpBlock = $this->isIpBlock($response_raw);
$response = json_decode($response_raw);
//Validate $response
if ($isIpBlock) {
update_option('rsssl_installation_error', 'cpanel:autossl', false);
$status = 'error';
$action = 'stop';
$message = __("Your website's ip address is blocked. Please add your domain's ip address to the security policy in CPanel","really-simple-ssl");
} else if (empty($response)) {
update_option('rsssl_installation_error', 'cpanel:autossl', false);
$status = 'error';
$action = 'skip';
$message = rsssl_get_manual_instructions_text($this->ssl_installation_url);
} else if ($response->status) {
delete_option('rsssl_installation_error');
$status = 'success';
$action = 'finalize';
$message = __("SSL successfully installed on $domains","really-simple-ssl");
} else {
update_option('rsssl_installation_error', 'cpanel:autossl', false);
$status = 'error';
$action = 'skip';//we try the default next
$message = __("Errors were reported during installation.","really-simple-ssl").'<br> '.$response->errors[0];
}
return new RSSSL_RESPONSE($status, $action, $message);
}
/**
* Connect to the cPanel using UAPI.
*
* @param string $request_uri
* @param null|array $payload
*
* @return mixed
*/
public function connectUapi($request_uri, $payload = null)
{
// Set up the cURL request object.
$ch = curl_init($request_uri);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, $this->username.':'.$this->password);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_BUFFERSIZE, 131072);
if (null !== $payload) {
// Set up a POST request with the payload.
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Make the call, and then terminate the cURL caller object.
$curl_response = curl_exec($ch);
if (curl_errno($ch)) {
$error_msg = curl_error($ch);
}
curl_close($ch);
//return output.
return $curl_response;
}
/**
* Set DNS TXT record using Json API through cPanel XMLAPI.
*
* @param string $domain
* @param string $value
*
* @return RSSSL_RESPONSE
*/
public function set_txt_record($domain, $value)
{
$args = [
'domain' => $domain,
'name' => '_acme-challenge',
'type' => 'TXT',
'txtdata' => $value,
'ttl' => '600',
'class' => 'IN',
'cpanel_jsonapi_user' => $this->username,
'cpanel_jsonapi_module' => 'ZoneEdit',
'cpanel_jsonapi_func' => 'add_zone_record',
'cpanel_jsonapi_apiversion' => '2',
];
$args = http_build_query($args, '', '&');
$url = 'https://'.$this->host.':2083/json-api/cpanel';
$authstr = 'Authorization: Basic '.base64_encode($this->username.':'.$this->password)."\r\n";
$curl = curl_init();
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_BUFFERSIZE, 131072);
$header[0] = $authstr.
"Content-Type: application/x-www-form-urlencoded\r\n".
'Content-Length: '.\strlen($args)."\r\n"."\r\n".$args;
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_POST, 1);
$response = curl_exec($curl);
curl_close($curl);
if (false === $response) {
return new RSSSL_RESPONSE('error', 'stop', __("Unable to connect to cPanel", "really-simple-ssl").' '.curl_error($curl));
}
if (true === stristr($response, '<html>')) {
return new RSSSL_RESPONSE('error', 'stop', __("Login credentials incorrect", "really-simple-ssl"));
}
$response_array = json_decode($response, true);
if ( isset($response_array['cpanelresult']['data'][0]['result']['status']) ) {
if ($response_array['cpanelresult']['data'][0]['result']['status']) {
$status = 'success';
$action = 'continue';
$message = __("Successfully added TXT record.","really-simple-ssl");
} else {
$status = 'warning';
$action = 'continue';
$message = __("Could not automatically add TXT record. Please proceed manually, following the steps below.","really-simple-ssl");
if (isset($response_array['cpanelresult']['data'][0]['result']['statusmsg'])) {
$message .= '<br>'.$response_array['cpanelresult']['data'][0]['result']['statusmsg'];
}
}
return new RSSSL_RESPONSE($status, $action, $message);
}
$event_result = (bool) $response_array['cpanelresult']['event']['result'];
$preevent_result = isset($response_array['cpanelresult']['preevent']) ? (bool) $response_array['cpanelresult']['preevent']['result'] : true; //Some cPanel doesn't provide this key. In that case, ignore it by setting 'true'.
$postevent_result = isset($response_array['cpanelresult']['postevent']) ? (bool) $response_array['cpanelresult']['postevent']['result'] : true; //Some cPanel doesn't provide this key. In that case, ignore it by setting 'true'.
if ($event_result && $preevent_result && $postevent_result) {
$status = 'success';
$action = 'continue';
$message = __("Successfully added TXT record.","really-simple-ssl");
} else {
$status = 'warning';
$action = 'continue';
$message = __("Could not automatically add TXT record. Please proceed manually, following the steps below.","really-simple-ssl");
}
return new RSSSL_RESPONSE($status, $action, $message);
}
}

View File

@@ -0,0 +1,117 @@
<?php
defined( 'ABSPATH' ) or die();
function rsssl_install_cpanel_autossl(){
if ( rsssl_is_ready_for('installation') ) {
$cpanel = new rsssl_cPanel();
$domains = RSSSL_LE()->letsencrypt_handler->get_subjects();
$response = $cpanel->enableAutoSSL($domains);
if ( $response->status === 'success' ) {
update_option('rsssl_le_certificate_installed_by_rsssl', 'cpanel:autossl', false);
}
return $response;
}
$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);
}
function rsssl_install_cpanel_default(){
if (rsssl_is_ready_for('installation')) {
$cpanel = new rsssl_cPanel();
$domains = RSSSL_LE()->letsencrypt_handler->get_subjects();
$response = $cpanel->installSSL($domains);
if ( $response->status === 'success' ) {
update_option('rsssl_le_certificate_installed_by_rsssl', 'cpanel:default', false);
}
return $response;
}
$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);
}
function rsssl_cpanel_set_txt_record(){
if ( rsssl_is_ready_for('dns-verification') ) {
$cpanel = new rsssl_cPanel();
$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 $domain => $token){
if (strpos($domain, '*') !== false) continue;
$response = $cpanel->set_txt_record($domain, $token);
}
if ( $response->status === 'success' ) {
update_option('rsssl_le_dns_configured_by_rsssl', true, false);
}
return $response;
} else {
$status = 'error';
$action = 'stop';
$message = __("The system is not ready for the DNS verification yet. Please run the wizard again.", "really-simple-ssl");
return new RSSSL_RESPONSE($status, $action, $message);
}
}
/**
* Add actions for cpanel
* @param array $fields
*
* @return array
*/
function rsssl_cpanel_add_condition_actions($fields){
$cpanel = new rsssl_cPanel();
if ( $cpanel->credentials_available() ) {
//this defaults to true, if not known.
$auto_ssl = RSSSL_LE()->hosts->host_api_supported( 'cpanel:autossl' );
$default_ssl = RSSSL_LE()->hosts->host_api_supported( 'cpanel:default' );
$installation_index = array_search( 'installation', array_column( $fields, 'id' ) );
$dns_index = array_search( 'dns-verification', array_column( $fields, 'id' ) );
//clear existing array
if ( $auto_ssl || $default_ssl ) {
$fields[ $installation_index ]['actions'] = array();
}
if ( $auto_ssl ) {
$fields[ $installation_index ]['actions'][] = [
'description' => __( "Attempting to install certificate using AutoSSL...", "really-simple-ssl" ),
'action' => 'rsssl_install_cpanel_autossl',
'attempts' => 1,
'status' => 'inactive',
];
}
if ( $default_ssl ) {
$fields[ $dns_index ]['actions'][] = [
'description' => __( "Attempting to set DNS txt record...", "really-simple-ssl" ),
'action' => 'rsssl_cpanel_set_txt_record',
'attempts' => 1,
'status' => 'inactive',
];
$fields[ $installation_index ]['actions'][] = [
'description' => __( "Attempting to install certificate...", "really-simple-ssl" ),
'action' => 'rsssl_install_cpanel_default',
'attempts' => 1,
'status' => 'inactive',
];
}
}
return $fields;
}
add_filter("rsssl_fields", "rsssl_cpanel_add_condition_actions");

View File

@@ -0,0 +1,146 @@
<?php
defined( 'ABSPATH' ) or die();
require_once rsssl_path . 'lib/admin/class-encryption.php';
use RSSSL\lib\admin\Encryption;
/**
* @package DirectAdmin
* @author Rogier Lankhorst
* @copyright Copyright (C) 2021, Rogier Lankhorst
* @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3
* @link https://really-simple-ssl.com
* @since Class available since Release 5.0.0
*
*/
require_once( rsssl_le_path . 'integrations/directadmin/httpsocket.php' );
require_once( rsssl_le_path . 'integrations/directadmin/functions.php' );
class rsssl_directadmin {
use Encryption;
public $host;
private $login;
private $password;
public $ssl_installation_url;
/**
* Initiates the directadmin class.
*
*/
public function __construct() {
$password = $this->decrypt_if_prefixed( rsssl_get_option( 'directadmin_password' ) );
$host = rsssl_get_option( 'directadmin_host' );
$this->host = str_replace( array( 'http://', 'https://', ':2222' ), '', $host );
$this->login = rsssl_get_option( 'directadmin_username' );
$this->password = $password;
$this->ssl_installation_url = 'https://' . $this->host . "";
}
/**
* Check if all creds are available
* @return bool
*/
public function credentials_available(){
if (!empty($this->host) && !empty($this->password) && !empty($this->login)) {
return true;
}
return false;
}
public function installSSL( $domains ) {
$response = false;
if ( is_array($domains) && count($domains)>0 ) {
foreach( $domains as $domain ) {
$response_item = $this->installSSLPerDomain($domain);
//set on first iteration
if ( !$response ) {
$response = $response_item;
}
//override if not successfull, to always get the error.
if ( $response->status !== 'success' ) {
$response = $response_item;
}
}
}
if ( !$response ) {
$response = new RSSSL_RESPONSE('error', 'stop', __("No valid list of domains.", "really-simple-ssl"));
}
return $response;
}
/**
* Install certificate
*
* @param string $domain
*
* @return RSSSL_RESPONSE
*/
public function installSSLPerDomain( $domain ) {
$key_file = get_option( 'rsssl_private_key_path' );
$cert_file = get_option( 'rsssl_certificate_path' );
$cabundle_file = get_option( 'rsssl_intermediate_path' );
try {
$server_ssl=true;
$server_port=2222;
$sock = new HTTPSocket;
if ($server_ssl){
$sock->connect("ssl://".$this->host, $server_port);
} else {
$sock->connect($this->host, $server_port);
}
$sock->set_login($this->login, $this->password);
$sock->method = "POST";
$sock->query('/CMD_API_SSL',
array(
'domain' => $domain,
'action' => 'save',
'type' => 'paste',
'certificate' => file_get_contents( $key_file ) . file_get_contents( $cert_file )
));
$response = $sock->fetch_parsed_body();
//set a default error response
$status = 'warning';
$action = 'continue';
$message = rsssl_get_manual_instructions_text($this->ssl_installation_url);
//if successful, proceed to next step
if ( empty($response['details']) && stripos($response[0], 'Error' ) ) {
$sock->query('/CMD_SSL',
array(
'domain' => $domain,
'action' => 'save',
'type' => 'cacert',
'active' => 'yes',
'cacert' => file_get_contents( $cabundle_file )
));
$response = $sock->fetch_parsed_body();
if ( empty($response['details']) && stripos($response[0], 'Error' ) ) {
$status = 'success';
$action = 'finalize';
$message = sprintf(__("SSL successfully installed on %s","really-simple-ssl"), $domain);
update_option( 'rsssl_le_certificate_installed_by_rsssl', 'directadmin', false );
delete_option( 'rsssl_installation_error' );
}
}
} catch ( Exception $e ) {
update_option( 'rsssl_installation_error', 'directadmin', false );
$status = 'warning';
$action = 'continue';
$message = $e->getMessage();
}
return new RSSSL_RESPONSE( $status, $action, $message );
}
}

View File

@@ -0,0 +1,46 @@
<?php
defined( 'ABSPATH' ) or die();
function rsssl_install_directadmin(){
if (rsssl_is_ready_for('installation')) {
$directadmin = new rsssl_directadmin();
$domains = RSSSL_LE()->letsencrypt_handler->get_subjects();
$response = $directadmin->installSSL($domains);
if ( $response->status === 'success' ) {
update_option('rsssl_le_certificate_installed_by_rsssl', 'directadmin', false );
}
return $response;
} 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);
}
}
/**
* Add actions for direct admin
* @param array $fields
*
* @return array
*/
function rsssl_directadmin_add_condition_actions($fields){
$directadmin = new rsssl_directadmin();
if ( $directadmin->credentials_available() ) {
$index = array_search( 'installation', array_column( $fields, 'id' ) );
//clear existing array
$fields[ $index ]['actions'] = [];
$fields[ $index ]['actions'][]
= array(
'description' => __( "Attempting to install certificate...", "really-simple-ssl" ),
'action' => 'rsssl_install_directadmin',
'attempts' => 1,
'status' => 'inactive',
);
}
return $fields;
}
add_filter( 'rsssl_fields', 'rsssl_directadmin_add_condition_actions' );

View File

@@ -0,0 +1,441 @@
<?php
/**
* Socket communication class.
*
* Originally designed for use with DirectAdmin's API, this class will fill any HTTP socket need.
*
* Very, very basic usage:
* $Socket = new HTTPSocket;
* echo $Socket->get('http://user:pass@somesite.com/somedir/some.file?query=string&this=that');
*
* @author Phi1 'l0rdphi1' Stier <l0rdphi1@liquenox.net>
* @package HTTPSocket
* @version 3.0.4
*/
class HTTPSocket {
var $version = '3.0.4';
/* all vars are private except $error, $query_cache, and $doFollowLocationHeader */
var $method = 'GET';
var $remote_host;
var $remote_port;
var $remote_uname;
var $remote_passwd;
var $result;
var $result_header;
var $result_body;
var $result_status_code;
var $lastTransferSpeed;
var $bind_host;
var $error = array();
var $warn = array();
var $query_cache = array();
var $doFollowLocationHeader = TRUE;
var $redirectURL;
var $max_redirects = 5;
var $ssl_setting_message = 'DirectAdmin appears to be using SSL. Change your script to connect to ssl://';
var $extra_headers = array();
var $proxy = false;
var $proxy_headers = array();
/**
* Create server "connection".
*
*/
function connect($host, $port = '' )
{
if (!is_numeric($port))
{
$port = 80;
}
$this->remote_host = $host;
$this->remote_port = $port;
}
function bind( $ip = '' )
{
if ( $ip == '' )
{
$ip = $_SERVER['SERVER_ADDR'];
}
$this->bind_host = $ip;
}
/**
* Change the method being used to communicate.
*
* @param string|null request method. supports GET, POST, and HEAD. default is GET
*/
function set_method( $method = 'GET' )
{
$this->method = strtoupper($method);
}
/**
* Specify a username and password.
*
* @param string|null username. defualt is null
* @param string|null password. defualt is null
*/
function set_login( $uname = '', $passwd = '' )
{
if ( strlen($uname) > 0 )
{
$this->remote_uname = $uname;
}
if ( strlen($passwd) > 0 )
{
$this->remote_passwd = $passwd;
}
}
/**
* For pass through, this function writes the data in chunks.
*/
private function stream_chunk($ch, $data)
{
echo($data);
return strlen($data);
}
private function stream_header($ch, $data)
{
if (!preg_match('/^HTTP/i', $data))
{
header($data);
}
return strlen($data);
}
/**
* Query the server
*
* @param string containing properly formatted server API. See DA API docs and examples. Http:// URLs O.K. too.
* @param string|array query to pass to url
* @param int if connection KB/s drops below value here, will drop connection
*/
function query( $request, $content = '', $doSpeedCheck = 0 )
{
$this->error = $this->warn = array();
$this->result_status_code = NULL;
$is_ssl = FALSE;
// is our request a http:// ... ?
if (preg_match('!^http://!i',$request) || preg_match('!^https://!i',$request))
{
$location = parse_url($request);
if (preg_match('!^https://!i',$request))
{
$this->connect('https://'.$location['host'],$location['port']);
}
else
$this->connect('http://'.$location['host'],$location['port']);
$this->set_login($location['user'],$location['pass']);
$request = $location['path'];
$content = $location['query'];
if ( strlen($request) < 1 )
{
$request = '/';
}
}
if (preg_match('!^ssl://!i', $this->remote_host))
$this->remote_host = 'https://'.substr($this->remote_host, 6);
if (preg_match('!^tcp://!i', $this->remote_host))
$this->remote_host = 'http://'.substr($this->remote_host, 6);
if (preg_match('!^https://!i', $this->remote_host))
$is_ssl = TRUE;
$array_headers = array(
'Host' => ( $this->remote_port == 80 ? $this->remote_host : "$this->remote_host:$this->remote_port" ),
'Accept' => '*/*',
'Connection' => 'Close' );
foreach ( $this->extra_headers as $key => $value )
{
$array_headers[$key] = $value;
}
$this->result = $this->result_header = $this->result_body = '';
// was content sent as an array? if so, turn it into a string
if (is_array($content))
{
$pairs = array();
foreach ( $content as $key => $value )
{
$pairs[] = "$key=".urlencode($value);
}
$content = join('&',$pairs);
unset($pairs);
}
$OK = TRUE;
if ($this->method == 'GET' && isset($content) && $content != '')
$request .= '?'.$content;
$ch = curl_init($this->remote_host.':'.$this->remote_port.$request);
if ($is_ssl)
{
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //1
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); //2
//curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
}
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_USERAGENT, "HTTPSocket/$this->version");
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 100);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_HEADER, 1);
if ($this->proxy)
{
curl_setopt($ch, CURLOPT_RETURNTRANSFER,false);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLINFO_HEADER_OUT, false);
curl_setopt($ch, CURLOPT_BUFFERSIZE, 8192); // 8192
curl_setopt($ch, CURLOPT_WRITEFUNCTION, array($this, "stream_chunk"));
curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, "stream_header"));
}
curl_setopt($ch, CURLOPT_LOW_SPEED_LIMIT, 512);
curl_setopt($ch, CURLOPT_LOW_SPEED_TIME, 120);
// instance connection
if ($this->bind_host)
{
curl_setopt($ch, CURLOPT_INTERFACE, $this->bind_host);
}
// if we have a username and password, add the header
if ( isset($this->remote_uname) && isset($this->remote_passwd) )
{
curl_setopt($ch, CURLOPT_USERPWD, $this->remote_uname.':'.$this->remote_passwd);
}
// for DA skins: if $this->remote_passwd is NULL, try to use the login key system
if ( isset($this->remote_uname) && $this->remote_passwd == NULL )
{
curl_setopt($ch, CURLOPT_COOKIE, "session={$_SERVER['SESSION_ID']}; key={$_SERVER['SESSION_KEY']}");
}
// if method is POST, add content length & type headers
if ( $this->method == 'POST' )
{
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $content);
//$array_headers['Content-type'] = 'application/x-www-form-urlencoded';
$array_headers['Content-length'] = strlen($content);
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $array_headers);
if( !($this->result = curl_exec($ch)) )
{
$this->error[] .= curl_error($ch);
$OK = FALSE;
}
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$this->result_header = substr($this->result, 0, $header_size);
$this->result_body = substr($this->result, $header_size);
$this->result_status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$this->lastTransferSpeed = curl_getinfo($ch, CURLINFO_SPEED_DOWNLOAD) / 1024;
curl_close($ch);
$this->query_cache[] = $this->remote_host.':'.$this->remote_port.$request;
$headers = $this->fetch_header();
// did we get the full file?
if ( !empty($headers['content-length']) && $headers['content-length'] != strlen($this->result_body) )
{
$this->result_status_code = 206;
}
// now, if we're being passed a location header, should we follow it?
if ($this->doFollowLocationHeader)
{
//dont bother if we didn't even setup the script correctly
if (isset($headers['x-use-https']) && $headers['x-use-https']=='yes')
die($this->ssl_setting_message);
if (isset($headers['location']))
{
if ($this->max_redirects <= 0)
die("Too many redirects on: ".$headers['location']);
$this->max_redirects--;
$this->redirectURL = $headers['location'];
$this->query($headers['location']);
}
}
}
function getTransferSpeed()
{
return $this->lastTransferSpeed;
}
/**
* The quick way to get a URL's content :)
*
* @param string URL
* @param boolean return as array? (like PHP's file() command)
* @return string result body
*/
function get($location, $asArray = FALSE )
{
$this->query($location);
if ( $this->get_status_code() == 200 )
{
if ($asArray)
{
return preg_split("/\n/",$this->fetch_body());
}
return $this->fetch_body();
}
return FALSE;
}
/**
* Returns the last status code.
* 200 = OK;
* 403 = FORBIDDEN;
* etc.
*
* @return int status code
*/
function get_status_code()
{
return $this->result_status_code;
}
/**
* Adds a header, sent with the next query.
*
* @param string header name
* @param string header value
*/
function add_header($key,$value)
{
$this->extra_headers[$key] = $value;
}
/**
* Clears any extra headers.
*
*/
function clear_headers()
{
$this->extra_headers = array();
}
/**
* Return the result of a query.
*
* @return string result
*/
function fetch_result()
{
return $this->result;
}
/**
* Return the header of result (stuff before body).
*
* @param string (optional) header to return
* @return array result header
*/
function fetch_header( $header = '' )
{
if ($this->proxy)
return $this->proxy_headers;
$array_headers = preg_split("/\r\n/",$this->result_header);
$array_return = array( 0 => $array_headers[0] );
unset($array_headers[0]);
foreach ( $array_headers as $pair )
{
if ($pair == '' || $pair == "\r\n") continue;
list($key,$value) = preg_split("/: /",$pair,2);
$array_return[strtolower($key)] = $value;
}
if ( $header != '' )
{
return $array_return[strtolower($header)];
}
return $array_return;
}
/**
* Return the body of result (stuff after header).
*
* @return string result body
*/
function fetch_body()
{
return $this->result_body;
}
/**
* Return parsed body in array format.
*
* @return array result parsed
*/
function fetch_parsed_body()
{
parse_str($this->result_body,$x);
return $x;
}
/**
* Set a specifc message on how to change the SSL setting, in the event that it's not set correctly.
*/
function set_ssl_setting_message($str)
{
$this->ssl_setting_message = $str;
}
}

View File

@@ -0,0 +1,7 @@
<?php
defined( 'ABSPATH' ) or die();
/**
* On hostgator, we don't have the cpanel api, so remove these steps.
* This is managed in the config, in the hosts array
*/

View File

@@ -0,0 +1 @@
<?php // You don't belong here. ?>

View File

@@ -0,0 +1,17 @@
<?php defined( 'ABSPATH' ) or die();
$other_host = rsssl_get_other_host();
if (file_exists( rsssl_le_path . "integrations/$other_host/$other_host.php" )) {
require_once( rsssl_le_path . "integrations/$other_host/$other_host.php" );
}
if (file_exists( rsssl_le_path . "integrations/$other_host/functions.php" )){
require_once( rsssl_le_path . "integrations/$other_host/functions.php" );
}
if ( rsssl_is_cpanel() ) {
require_once( rsssl_le_path . 'integrations/cpanel/cpanel.php' );
} else if ( rsssl_is_plesk() ) {
require_once( rsssl_le_path . 'integrations/plesk/plesk.php' );
} else if ( rsssl_is_directadmin() ) {
require_once( rsssl_le_path . 'integrations/directadmin/directadmin.php' );
}

View File

@@ -0,0 +1,43 @@
<?php
defined( 'ABSPATH' ) or die();
function rsssl_plesk_install(){
if (rsssl_is_ready_for('installation')) {
$cpanel = new rsssl_plesk();
$domains = RSSSL_LE()->letsencrypt_handler->get_subjects();
$response = $cpanel->installSSL($domains);
if ( $response->status === 'success' ) {
update_option('rsssl_le_certificate_installed_by_rsssl', 'cpanel:default', false);
}
return $response;
} 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);
}
}
/**
* Add the step to install SSL using Plesk
* @param array $fields
*
* @return array
*/
function rsssl_plesk_add_installation_step($fields){
$plesk = new rsssl_plesk();
if ( $plesk->credentials_available() ) {
$index = array_search( 'installation', array_column( $fields, 'id' ) );
$fields[ $index ]['actions'] = array_merge(array(
array(
'description' => __("Installing SSL certificate using PLESK API...", "really-simple-ssl"),
'action'=> 'rsssl_plesk_install',
'attempts' => 1,
'status' => 'inactive',
)
) , $fields[ $index ]['actions'] );
}
return $fields;
}
add_filter( 'rsssl_fields', 'rsssl_plesk_add_installation_step' );

View File

@@ -0,0 +1,101 @@
<?php
/**
* @package PLESK
* @author Rogier Lankhorst
* @copyright Copyright (C) 2021, Rogier Lankhorst
* @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3
* @link https://really-simple-ssl.com
* @since Class available since Release 5.0.0
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use PleskX\Api\Client;
require_once rsssl_path . 'lib/admin/class-encryption.php';
use RSSSL\lib\admin\Encryption;
require_once rsssl_le_path . 'vendor/autoload.php';
require_once( rsssl_le_path . 'integrations/plesk/functions.php' );
class rsssl_plesk
{
use Encryption;
public $host;
private $login;
private $password;
public $ssl_installation_url;
/**
* Initiates the Plesk class.
*
*/
public function __construct()
{
$password = $this->decrypt_if_prefixed( rsssl_get_option('plesk_password') );
$host = rsssl_get_option('plesk_host');
$this->host = str_replace(array('http://', 'https://', ':8443'), '', $host);
$this->login = rsssl_get_option('plesk_username');
$this->password = $password;
$this->ssl_installation_url = 'https://'.$this->host.":8443/smb/ssl-certificate/list/id/21";
}
/**
* Check if all creds are available
* @return bool
*/
public function credentials_available(){
if (!empty($this->host) && !empty($this->password) && !empty($this->login)) {
return true;
}
return false;
}
/**
* Install certificate
* @param $domains
*
* @return RSSSL_RESPONSE
*/
public function installSSL($domains){
$key_file = get_option('rsssl_private_key_path');
$cert_file = get_option('rsssl_certificate_path');
$cabundle_file = get_option('rsssl_intermediate_path');
try {
$client = new Client($this->host);
$client->setCredentials($this->login, $this->password);
$response = $client->certificate()->install($domains, [
'csr' => '',
'pvt' => file_get_contents($key_file),
'cert' => file_get_contents($cert_file),
'ca' => file_get_contents($cabundle_file),
]);
update_option('rsssl_le_certificate_installed_by_rsssl', 'plesk', false);
delete_option('rsssl_installation_error' );
$status = 'success';
$action = 'continue';
$message = __('Successfully installed SSL',"really-simple-ssl");
} catch(Exception $e) {
update_option('rsssl_installation_error', 'plesk', false);
$status = 'warning';
$action = 'continue';
$message = $e->getMessage();
}
return new RSSSL_RESPONSE($status, $action, $message);
}
}

View File

@@ -0,0 +1,122 @@
<?php
defined('ABSPATH') or die();
/**
* Capability handling for Let's Encrypt
* @return bool
*
* php -r "readfile('https://getcomposer.org/installer');" | php
*/
if (!function_exists('rsssl_letsencrypt_generation_allowed')) {
function rsssl_letsencrypt_generation_allowed($strict = false) {
$certificateGeneratedByRsssl = get_option('rsssl_le_certificate_generated_by_rsssl');
/**
* LE classes should also run if SSL is generated by rsssl, the plus one
* cache is cleared or when the cron runs
*/
if ($certificateGeneratedByRsssl && (
!get_option('rsssl_plusone_count') || wp_doing_cron()
)) {
return apply_filters('rsssl_letsencrypt_generation_allowed', true, $strict);
}
if (current_user_can('manage_security') === false) {
return apply_filters('rsssl_letsencrypt_generation_allowed', false, $strict);
}
if ( isset($_GET['letsencrypt'])) {
return apply_filters('rsssl_letsencrypt_generation_allowed', true, $strict);
}
/**
* Filter: 'rsssl_letsencrypt_generation_allowed'
*
* Can be used to override the default behavior of allowing or
* disallowing Let's Encrypt certificate generation.
*
* @param bool $allowed
* @param bool $strict
* @return bool
*/
return apply_filters('rsssl_letsencrypt_generation_allowed', false, $strict);
}
}
class RSSSL_LETSENCRYPT {
private static $instance;
public $le_restapi;
public $field;
public $hosts;
public $letsencrypt_handler;
private function __construct() {
}
public static function instance() {
if ( ! isset( self::$instance ) && ! ( self::$instance instanceof RSSSL_LETSENCRYPT ) ) {
self::$instance = new RSSSL_LETSENCRYPT;
self::$instance->setup_constants();
self::$instance->includes();
if ( rsssl_letsencrypt_generation_allowed() ) {
self::$instance->hosts = new rsssl_le_hosts();
self::$instance->letsencrypt_handler = new rsssl_letsencrypt_handler();
self::$instance->le_restapi = new rsssl_le_restapi();
}
}
return self::$instance;
}
private function setup_constants() {
define('rsssl_le_url', plugin_dir_url(__FILE__));
define('rsssl_le_path', trailingslashit(plugin_dir_path(__FILE__)));
}
private function includes() {
require_once( rsssl_le_path . 'functions.php');
if ( rsssl_letsencrypt_generation_allowed() ) {
require_once( rsssl_le_path . 'config/class-hosts.php' );
require_once( rsssl_le_path . 'config/fields.php');
require_once( rsssl_le_path . 'class-le-restapi.php' );
require_once( rsssl_le_path . 'class-letsencrypt-handler.php' );
require_once( rsssl_le_path . 'integrations/integrations.php' );
}
require_once( rsssl_le_path . 'config/notices.php' );
}
/**
* Notice about possible compatibility issues with add ons
*/
public static function admin_notices() {
}
}
function RSSSL_LE() {
return RSSSL_LETSENCRYPT::instance();
}
add_action( 'plugins_loaded', 'RSSSL_LE', 9 );
class RSSSL_RESPONSE
{
public $message;
public $action;
public $status;
public $output;
public $request_success;
public function __construct($status, $action, $message, $output = false )
{
$this->status = $status;
$this->action = $action;
$this->message = $message;
$this->output = $output;
$this->request_success = true;
}
}

View File

@@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInita8412ede23fd11b4d0e29303fdebd5f4::getLoader();

View File

@@ -0,0 +1,479 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
private $vendorDir;
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
private static $registeredLoaders = array();
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
}
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders indexed by their corresponding vendor directories.
*
* @return self[]
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}

View File

@@ -0,0 +1,303 @@
<?php
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
class InstalledVersions
{
private static $installed = array (
'root' =>
array (
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'aliases' =>
array (
),
'reference' => '53cfe01c831d81b1398d479a9e85cbb4110e9e13',
'name' => '__root__',
),
'versions' =>
array (
'__root__' =>
array (
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'aliases' =>
array (
),
'reference' => '53cfe01c831d81b1398d479a9e85cbb4110e9e13',
),
'fbett/le_acme2' =>
array (
'pretty_version' => '1.5.6',
'version' => '1.5.6.0',
'aliases' =>
array (
),
'reference' => '26b2c421764b173326f6bcb0713a86bd614f77fa',
),
'plesk/api-php-lib' =>
array (
'pretty_version' => 'v1.0.7',
'version' => '1.0.7.0',
'aliases' =>
array (
),
'reference' => '7f81b0c3bb0a9f4200aef62a54d3e2c04d91a605',
),
),
);
private static $canGetVendors;
private static $installedByVendor = array();
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
public static function isInstalled($packageName)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return true;
}
}
return false;
}
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints($constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
public static function getRawData()
{
return self::$installed;
}
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
}
}
}
$installed[] = self::$installed;
return $installed;
}
}

View File

@@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,10 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
);

View File

@@ -0,0 +1,10 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'LE_ACME2' => array($vendorDir . '/fbett/le_acme2/src'),
);

View File

@@ -0,0 +1,10 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'PleskX\\' => array($vendorDir . '/plesk/api-php-lib/src'),
);

View File

@@ -0,0 +1,57 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInita8412ede23fd11b4d0e29303fdebd5f4
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInita8412ede23fd11b4d0e29303fdebd5f4', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInita8412ede23fd11b4d0e29303fdebd5f4', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInita8412ede23fd11b4d0e29303fdebd5f4::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
return $loader;
}
}

View File

@@ -0,0 +1,47 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInita8412ede23fd11b4d0e29303fdebd5f4
{
public static $prefixLengthsPsr4 = array (
'P' =>
array (
'PleskX\\' => 7,
),
);
public static $prefixDirsPsr4 = array (
'PleskX\\' =>
array (
0 => __DIR__ . '/..' . '/plesk/api-php-lib/src',
),
);
public static $prefixesPsr0 = array (
'L' =>
array (
'LE_ACME2' =>
array (
0 => __DIR__ . '/..' . '/fbett/le_acme2/src',
),
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInita8412ede23fd11b4d0e29303fdebd5f4::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInita8412ede23fd11b4d0e29303fdebd5f4::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInita8412ede23fd11b4d0e29303fdebd5f4::$prefixesPsr0;
$loader->classMap = ComposerStaticInita8412ede23fd11b4d0e29303fdebd5f4::$classMap;
}, null, ClassLoader::class);
}
}

View File

@@ -0,0 +1 @@
<?php // You don't belong here. ?>

View File

@@ -0,0 +1,113 @@
{
"packages": [
{
"name": "fbett/le_acme2",
"version": "1.5.6",
"version_normalized": "1.5.6.0",
"source": {
"type": "git",
"url": "https://github.com/fbett/le-acme2-php.git",
"reference": "26b2c421764b173326f6bcb0713a86bd614f77fa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fbett/le-acme2-php/zipball/26b2c421764b173326f6bcb0713a86bd614f77fa",
"reference": "26b2c421764b173326f6bcb0713a86bd614f77fa",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"ext-openssl": "*",
"php": ">=7.3"
},
"time": "2021-05-17T07:08:46+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-0": {
"LE_ACME2": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabian Bett",
"homepage": "https://www.bett-ingenieure.de",
"role": "Developer"
}
],
"description": "Letsencrypt PHP ACME v2 client",
"homepage": "https://github.com/fbett/le-acme2-php",
"support": {
"issues": "https://github.com/fbett/le-acme2-php/issues",
"source": "https://github.com/fbett/le-acme2-php/tree/v1.5.6"
},
"install-path": "../fbett/le_acme2"
},
{
"name": "plesk/api-php-lib",
"version": "v1.0.7",
"version_normalized": "1.0.7.0",
"source": {
"type": "git",
"url": "https://github.com/plesk/api-php-lib.git",
"reference": "7f81b0c3bb0a9f4200aef62a54d3e2c04d91a605"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/plesk/api-php-lib/zipball/7f81b0c3bb0a9f4200aef62a54d3e2c04d91a605",
"reference": "7f81b0c3bb0a9f4200aef62a54d3e2c04d91a605",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-simplexml": "*",
"ext-xml": "*",
"php": "^7.3"
},
"require-dev": {
"phpunit/phpunit": "^9",
"spatie/phpunit-watcher": "^1.22"
},
"time": "2020-12-24T07:20:26+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"PleskX\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Alexei Yuzhakov",
"email": "sibprogrammer@gmail.com"
},
{
"name": "Plesk International GmbH.",
"email": "plesk-dev-leads@plesk.com"
}
],
"description": "PHP object-oriented library for Plesk XML-RPC API",
"support": {
"issues": "https://github.com/plesk/api-php-lib/issues",
"source": "https://github.com/plesk/api-php-lib/tree/v1.0.7"
},
"install-path": "../plesk/api-php-lib"
}
],
"dev": true,
"dev-package-names": []
}

View File

@@ -0,0 +1,42 @@
<?php return array (
'root' =>
array (
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'aliases' =>
array (
),
'reference' => '53cfe01c831d81b1398d479a9e85cbb4110e9e13',
'name' => '__root__',
),
'versions' =>
array (
'__root__' =>
array (
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'aliases' =>
array (
),
'reference' => '53cfe01c831d81b1398d479a9e85cbb4110e9e13',
),
'fbett/le_acme2' =>
array (
'pretty_version' => '1.5.6',
'version' => '1.5.6.0',
'aliases' =>
array (
),
'reference' => '26b2c421764b173326f6bcb0713a86bd614f77fa',
),
'plesk/api-php-lib' =>
array (
'pretty_version' => 'v1.0.7',
'version' => '1.0.7.0',
'aliases' =>
array (
),
'reference' => '7f81b0c3bb0a9f4200aef62a54d3e2c04d91a605',
),
),
);

View File

@@ -0,0 +1,26 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 70100)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.1.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

View File

@@ -0,0 +1 @@
<?php // You don't belong here. ?>

View File

@@ -0,0 +1,19 @@
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,119 @@
# le-acme2-php
LetsEncrypt client library for ACME v2 written in PHP.
This library is inspired by [yourivw/LEClient](https://github.com/yourivw/LEClient), completely rewritten and enhanced with some new features:
- Support for Composer autoload (including separated Namespaces)
- Automatic renewal process
- Managed HTTP authentication process
- Response caching mechanism
- Prevents blocking while waiting for server results
- Optional certificate feature "OCSP Must-Staple"
- Optional set a preferred chain
The aim of this client is to make an easy-to-use and integrated solution to create a LetsEncrypt-issued SSL/TLS certificate with PHP.
You have the possibility to use the HTTP authentication:
You need to be able to redirect specific requests (see below)
You have also the possibility to use DNS authentication:
You need to be able to set dynamic DNS configurations.
Wildcard certificates can only be requested by using the dns authentication.
## Current version
Tested with LetsEncrypt staging and production servers.
[Transitioning to ISRG's Root](https://letsencrypt.org/2019/04/15/transitioning-to-isrg-root.html):
This library supports it to set a preferred chain in `Order::setPreferredChain($issuerCN))`.
If the preferred chain is not set or set to IdenTrusts chain,
this library will try to use the IdenTrusts chain as long as possible.
Please see: https://letsencrypt.org/docs/dst-root-ca-x3-expiration-september-2021/
## Prerequisites
The minimum required PHP version is 7.3.
This client also depends on cURL and OpenSSL.
## Getting Started
Install via composer:
```
composer require fbett/le_acme2
```
Also have a look at the [LetsEncrypt documentation](https://letsencrypt.org/docs/) for more information and documentation on LetsEncrypt and ACME.
## Example Integration
- Create a working directory.
Warning: This directory will also include private keys, so i suggest to place this directory somewhere not in the root document path of the web server.
Additionally this directory should be protected to be read from other web server users.
```
mkdir /etc/ssl/le-storage/
chown root:root /etc/ssl/le-storage
chmod 0600 /etc/ssl/le-storage
```
- (HTTP authorization only) Create a directory for the acme challenges. It must be reachable by http/https.
```
mkdir /var/www/acme-challenges
```
- (HTTP authorization only) Redirect specific requests to your acme-challenges directory
Example apache virtual host configuration:
```
<VirtualHost ...>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule \.well-known/acme-challenge/(.*)$ https://your-domain.com/path/to/acme-challenges/$1 [R=302,L]
</IfModule>
</VirtualHost>
```
- (DNS authorization only) Set the DNS configuration
If `DNSWriter::write(...)` is called, set the DNS configuration like described in:
[https://letsencrypt.org/docs/challenge-types/#dns-01-challenge](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge)
(By adding the digest as a TXT record for the subdomain '_acme-challenge'.)
- Use the certificate bundle, if the certificate is issued:
```
if($order->isCertificateBundleAvailable()) {
$bundle = $order->getCertificateBundle();
$pathToPrivateKey = $bundle->path . $bundle->private;
$pathToCertificate = $bundle->path . $bundle->certificate;
$pathToIntermediate = $bundle->path . $bundle->intermediate;
$order->enableAutoRenewal(); // If the date of expiration is closer than thirty days, the order will automatically start the renewal process.
}
```
If a certificate is renewed, the path will also change.
My integrated workflow is the following:
- User enables SSL to a specific domain in my control panel
- The cronjob of this control panel will detect these changes and tries to create or get an order like in the sample.
- The cronjob will fetch the information within the certificate bundle, if the certificate bundle is ready (mostly on the second run for challenge type HTTP and on the third run for challenge type DNS)
- The cronjob will also build the Apache virtual host files and will restart the Apache2 service, if the new config file is different.
Please take a look on the Samples for a full sample workflow.
## License
This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details.

View File

@@ -0,0 +1,25 @@
{
"name": "fbett/le_acme2",
"description": "Letsencrypt PHP ACME v2 client",
"homepage": "https://github.com/fbett/le-acme2-php",
"version": "1.5.6",
"license": "MIT",
"authors": [
{
"name": "Fabian Bett",
"homepage": "https://www.bett-ingenieure.de",
"role": "Developer"
}
],
"autoload": {
"psr-0": {
"LE_ACME2": "src/"
}
},
"require": {
"php": ">=7.3",
"ext-curl": "*",
"ext-openssl": "*",
"ext-json": "*"
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace LE_ACME2;
defined('ABSPATH') or die();
use LE_ACME2\Connector\Connector;
abstract class AbstractKeyValuable {
const KEY_TYPE_RSA = "RSA";
const KEY_TYPE_EC = "EC";
protected $_identifier;
protected static $_directoryPath = null;
public static function setCommonKeyDirectoryPath(string $directoryPath) {
if(!file_exists($directoryPath)) {
throw new \RuntimeException('Common Key Directory Path does not exist');
}
self::$_directoryPath = realpath($directoryPath) . DIRECTORY_SEPARATOR;
}
public static function getCommonKeyDirectoryPath() : ?string {
return self::$_directoryPath;
}
protected function _getKeyDirectoryPath(string $appendix = '') : string {
return self::$_directoryPath . $this->_identifier . $appendix . DIRECTORY_SEPARATOR;
}
public function getKeyDirectoryPath() : string {
return $this->_getKeyDirectoryPath('');
}
protected function _initKeyDirectory(string $keyType = self::KEY_TYPE_RSA, bool $ignoreIfKeysExist = false) {
if(!file_exists($this->getKeyDirectoryPath())) {
mkdir($this->getKeyDirectoryPath(), 0755, true);
}
if(!$ignoreIfKeysExist && (
file_exists($this->getKeyDirectoryPath() . 'private.pem') ||
file_exists($this->getKeyDirectoryPath() . 'public.pem')
)
) {
throw new \RuntimeException(
'Keys exist already. Exists the ' . get_class($this) . ' already?' . PHP_EOL .
'Path: ' . $this->getKeyDirectoryPath()
);
}
if($keyType == self::KEY_TYPE_RSA) {
Utilities\KeyGenerator::RSA(
$this->getKeyDirectoryPath(),
'private.pem',
'public.pem'
);
} else if($keyType == self::KEY_TYPE_EC) {
Utilities\KeyGenerator::EC(
$this->getKeyDirectoryPath(),
'private.pem',
'public.pem'
);
} else {
throw new \RuntimeException('Key type "' . $keyType . '" not supported.');
}
}
protected function _clearKeyDirectory() {
if(file_exists($this->getKeyDirectoryPath() . 'private.pem')) {
unlink($this->getKeyDirectoryPath() . 'private.pem');
}
if(file_exists($this->getKeyDirectoryPath() . 'public.pem')) {
unlink($this->getKeyDirectoryPath() . 'public.pem');
}
}
protected function _getAccountIdentifier(Account $account) : string {
$staging = Connector::getInstance()->isUsingStagingServer();
return 'account_' . ($staging ? 'staging_' : 'live_') . $account->getEmail();
}
}

View File

@@ -0,0 +1,163 @@
<?php
namespace LE_ACME2;
defined('ABSPATH') or die();
use LE_ACME2\Request;
use LE_ACME2\Response;
use LE_ACME2\Utilities;
use LE_ACME2\Exception;
class Account extends AbstractKeyValuable {
private $_email = NULL;
public function __construct(string $email) {
$this->_setEmail($email);
Utilities\Logger::getInstance()->add(
Utilities\Logger::LEVEL_INFO,
get_class() . '::' . __FUNCTION__ . ' email: "' . $email . '"'
);
Utilities\Logger::getInstance()->add(
Utilities\Logger::LEVEL_DEBUG,
get_class() . '::' . __FUNCTION__ . ' path: ' . $this->getKeyDirectoryPath()
);
}
private function _setEmail(string $email) {
$this->_email = $email;
$this->_identifier = $this->_getAccountIdentifier($this);
}
public function getEmail() : string {
return $this->_email;
}
/**
* @param string $email
* @return Account|null
* @throws Exception\AbstractException
*/
public static function create(string $email) : Account {
$account = new self($email);
$account->_initKeyDirectory();
$request = new Request\Account\Create($account);
try {
$response = $request->getResponse();
Cache\AccountResponse::getInstance()->set($account, $response);
return $account;
} catch(Exception\AbstractException $e) {
$account->_clearKeyDirectory();
throw $e;
}
}
public static function exists(string $email) : bool {
$account = new self($email);
return file_exists($account->getKeyDirectoryPath()) &&
file_exists($account->getKeyDirectoryPath() . 'private.pem') &&
file_exists($account->getKeyDirectoryPath() . 'public.pem');
}
public static function get(string $email) : Account {
$account = new self($email);
if(!self::exists($email))
throw new \RuntimeException('Keys not found - does this account exist?');
return $account;
}
/**
* @return Response\AbstractResponse|Response\Account\GetData
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function getData() : Response\Account\GetData {
$request = new Request\Account\GetData($this);
return $request->getResponse();
}
/**
* @param string $email
* @return bool
* @throws Exception\RateLimitReached
*/
public function update(string $email) : bool {
$request = new Request\Account\Update($this, $email);
try {
/* $response = */ $request->getResponse();
$previousKeyDirectoryPath = $this->getKeyDirectoryPath();
$this->_setEmail($email);
if($previousKeyDirectoryPath != $this->getKeyDirectoryPath())
rename($previousKeyDirectoryPath, $this->getKeyDirectoryPath());
return true;
} catch(Exception\InvalidResponse $e) {
return false;
}
}
/**
* @return bool
* @throws Exception\RateLimitReached
*/
public function changeKeys() : bool {
Utilities\KeyGenerator::RSA($this->getKeyDirectoryPath(), 'private-replacement.pem', 'public-replacement.pem');
$request = new Request\Account\ChangeKeys($this);
try {
/* $response = */ $request->getResponse();
unlink($this->getKeyDirectoryPath() . 'private.pem');
unlink($this->getKeyDirectoryPath() . 'public.pem');
rename($this->getKeyDirectoryPath() . 'private-replacement.pem', $this->getKeyDirectoryPath() . 'private.pem');
rename($this->getKeyDirectoryPath() . 'public-replacement.pem', $this->getKeyDirectoryPath() . 'public.pem');
return true;
} catch(Exception\InvalidResponse $e) {
return false;
}
}
/**
* @return bool
* @throws Exception\RateLimitReached
*/
public function deactivate() : bool {
$request = new Request\Account\Deactivate($this);
try {
/* $response = */ $request->getResponse();
return true;
} catch(Exception\InvalidResponse $e) {
return false;
}
}
}

View File

@@ -0,0 +1,174 @@
<?php
namespace LE_ACME2\Authorizer;
defined('ABSPATH') or die();
use LE_ACME2\Request;
use LE_ACME2\Response;
use LE_ACME2\Cache;
use LE_ACME2\Utilities;
use LE_ACME2\Exception;
use LE_ACME2\Account;
use LE_ACME2\Order;
abstract class AbstractAuthorizer {
protected $_account;
protected $_order;
/**
* AbstractAuthorizer constructor.
*
* @param Account $account
* @param Order $order
*
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
* @throws Exception\ExpiredAuthorization
*/
public function __construct(Account $account, Order $order) {
$this->_account = $account;
$this->_order = $order;
$this->_fetchAuthorizationResponses();
}
/** @var Response\Authorization\Get[] $_authorizationResponses */
protected $_authorizationResponses = [];
/**
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
* @throws Exception\ExpiredAuthorization
*/
protected function _fetchAuthorizationResponses() {
if(!file_exists($this->_order->getKeyDirectoryPath() . 'private.pem')) {
Utilities\Logger::getInstance()->add(
Utilities\Logger::LEVEL_DEBUG,
get_class() . '::' . __FUNCTION__ . ' result suppressed (Order has finished already)'
);
return;
}
$orderResponse = Cache\OrderResponse::getInstance()->get($this->_order);
foreach($orderResponse->getAuthorizations() as $authorization) {
$request = new Request\Authorization\Get($this->_account, $authorization);
$this->_authorizationResponses[] = $request->getResponse();
}
}
protected function _hasValidAuthorizationResponses() : bool {
return count($this->_authorizationResponses) > 0;
}
public function shouldStartAuthorization() : bool {
foreach($this->_authorizationResponses as $response) {
$challenge = $response->getChallenge($this->_getChallengeType());
if($challenge->status == Response\Authorization\Struct\Challenge::STATUS_PENDING) {
Utilities\Logger::getInstance()->add(
Utilities\Logger::LEVEL_DEBUG,
get_class() . '::' . __FUNCTION__ . ' "Pending challenge found',
$challenge
);
return true;
}
}
return false;
}
abstract protected function _getChallengeType() : string;
/**
* @throws Exception\AuthorizationInvalid
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
* @throws Exception\ExpiredAuthorization
*/
public function progress() {
if(!$this->_hasValidAuthorizationResponses())
return;
$existsNotValidChallenges = false;
foreach($this->_authorizationResponses as $authorizationResponse) {
$challenge = $authorizationResponse->getChallenge($this->_getChallengeType());
if($this->_existsNotValidChallenges($challenge, $authorizationResponse)) {
$existsNotValidChallenges = true;
}
}
$this->_finished = !$existsNotValidChallenges;
}
/**
* @param Response\Authorization\Struct\Challenge $challenge
* @param Response\Authorization\Get $authorizationResponse
* @return bool
*
* @throws Exception\AuthorizationInvalid
*/
protected function _existsNotValidChallenges(Response\Authorization\Struct\Challenge $challenge,
Response\Authorization\Get $authorizationResponse
) : bool {
if($challenge->status == Response\Authorization\Struct\Challenge::STATUS_PENDING) {
Utilities\Logger::getInstance()->add(
Utilities\Logger::LEVEL_DEBUG,
get_class() . '::' . __FUNCTION__ . ' "Non valid challenge found',
$challenge
);
return true;
}
else if($challenge->status == Response\Authorization\Struct\Challenge::STATUS_PROGRESSING) {
// Should come back later
return true;
}
else if($challenge->status == Response\Authorization\Struct\Challenge::STATUS_VALID) {
}
else if($challenge->status == Response\Authorization\Struct\Challenge::STATUS_INVALID) {
throw new Exception\AuthorizationInvalid(
'Received status "' . Response\Authorization\Struct\Challenge::STATUS_INVALID . '" while challenge should be verified'
);
}
else {
throw new \RuntimeException('Challenge status "' . $challenge->status . '" is not implemented');
}
return false;
}
protected $_finished = false;
public function hasFinished() : bool {
Utilities\Logger::getInstance()->add(
Utilities\Logger::LEVEL_DEBUG,
get_called_class() . '::' . __FUNCTION__,
$this->_finished
);
return $this->_finished;
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace LE_ACME2\Authorizer;
defined('ABSPATH') or die();
use LE_ACME2\Order;
abstract class AbstractDNSWriter {
/**
* @param Order $order
* @param string $identifier
* @param string $digest
*
* @return bool return true, if the dns configuration is usable and the process should be progressed
*/
abstract public function write(Order $order, string $identifier, string $digest) : bool;
}

View File

@@ -0,0 +1,70 @@
<?php
namespace LE_ACME2\Authorizer;
defined('ABSPATH') or die();
use LE_ACME2\Request;
use LE_ACME2\Response;
use LE_ACME2\Exception;
use LE_ACME2\Order;
use LE_ACME2\Struct\ChallengeAuthorizationKey;
use LE_ACME2\Utilities;
class DNS extends AbstractAuthorizer {
protected function _getChallengeType(): string {
return Order::CHALLENGE_TYPE_DNS;
}
/** @var AbstractDNSWriter $_dnsWriter */
private static $_dnsWriter = null;
public static function setWriter(AbstractDNSWriter $dnsWriter) : void {
self::$_dnsWriter = $dnsWriter;
}
/**
* @param Response\Authorization\Struct\Challenge $challenge
* @param Response\Authorization\Get $authorizationResponse
* @return bool
*
* @throws Exception\AuthorizationInvalid
* @throws Exception\DNSAuthorizationInvalid
* @throws Exception\ExpiredAuthorization
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
protected function _existsNotValidChallenges(Response\Authorization\Struct\Challenge $challenge,
Response\Authorization\Get $authorizationResponse
) : bool {
if($challenge->status == Response\Authorization\Struct\Challenge::STATUS_PENDING) {
if(self::$_dnsWriter === null) {
throw new \RuntimeException('DNS writer is not set');
}
if( self::$_dnsWriter->write(
$this->_order,
$authorizationResponse->getIdentifier()->value,
(new ChallengeAuthorizationKey($this->_account))->getEncoded($challenge->token)
)
) {
$request = new Request\Authorization\Start($this->_account, $this->_order, $challenge);
/* $response = */ $request->getResponse();
} else {
Utilities\Logger::getInstance()->add(Utilities\Logger::LEVEL_INFO, 'Pending challenge deferred');
}
}
if($challenge->status == Response\Authorization\Struct\Challenge::STATUS_INVALID) {
throw new Exception\DNSAuthorizationInvalid(
'Received status "' . Response\Authorization\Struct\Challenge::STATUS_INVALID . '" while challenge should be verified'
);
}
return parent::_existsNotValidChallenges($challenge, $authorizationResponse);
}
}

View File

@@ -0,0 +1,119 @@
<?php
namespace LE_ACME2\Authorizer;
defined('ABSPATH') or die();
use LE_ACME2\Request;
use LE_ACME2\Response;
use LE_ACME2\Struct\ChallengeAuthorizationKey;
use LE_ACME2\Utilities;
use LE_ACME2\Exception;
use LE_ACME2\Order;
class HTTP extends AbstractAuthorizer {
protected static $_directoryPath = null;
public static function setDirectoryPath(string $directoryPath) {
if(!file_exists($directoryPath)) {
throw new \RuntimeException('HTTP authorization directory path does not exist');
}
self::$_directoryPath = realpath($directoryPath) . DIRECTORY_SEPARATOR;
}
public static function getDirectoryPath() : ?string {
return self::$_directoryPath;
}
protected function _getChallengeType(): string {
return Order::CHALLENGE_TYPE_HTTP;
}
/**
* @param Response\Authorization\Struct\Challenge $challenge
* @param Response\Authorization\Get $authorizationResponse
* @return bool
*
* @throws Exception\AuthorizationInvalid
* @throws Exception\ExpiredAuthorization
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
protected function _existsNotValidChallenges(Response\Authorization\Struct\Challenge $challenge,
Response\Authorization\Get $authorizationResponse
) : bool {
Utilities\Logger::getInstance()->add(
Utilities\Logger::LEVEL_DEBUG,
'Challenge "' . $challenge->token . '" has status:' . $challenge->status
);
if($challenge->status == Response\Authorization\Struct\Challenge::STATUS_PENDING) {
$this->_writeToFile($challenge);
if($this->_validateFile($authorizationResponse->getIdentifier()->value, $challenge)) {
$request = new Request\Authorization\Start($this->_account, $this->_order, $challenge);
/* $response = */ $request->getResponse();
} else {
Utilities\Logger::getInstance()->add(Utilities\Logger::LEVEL_INFO, 'Could not validate HTTP Authorization file');
}
}
if($challenge->status == Response\Authorization\Struct\Challenge::STATUS_INVALID) {
throw new Exception\HTTPAuthorizationInvalid(
'Received status "' . Response\Authorization\Struct\Challenge::STATUS_INVALID . '" while challenge should be verified'
);
}
return parent::_existsNotValidChallenges($challenge, $authorizationResponse);
}
private function _writeToFile(Response\Authorization\Struct\Challenge $challenge) : void {
file_put_contents(
self::$_directoryPath . $challenge->token,
(new ChallengeAuthorizationKey($this->_account))->get($challenge->token)
);
}
/**
* @param string $domain
* @param Response\Authorization\Struct\Challenge $challenge
* @return bool
*
* @throws Exception\HTTPAuthorizationInvalid
*/
private function _validateFile(string $domain, Response\Authorization\Struct\Challenge $challenge) : bool {
if ( get_option('rsssl_skip_challenge_directory_request') ) {
return true;
}
$challengeAuthorizationKey = new ChallengeAuthorizationKey($this->_account);
$requestURL = 'http://' . $domain . '/.well-known/acme-challenge/' . $challenge->token;
$handle = curl_init();
curl_setopt($handle, CURLOPT_URL, $requestURL);
curl_setopt($handle, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($handle);
$result = !empty($response) && $response == $challengeAuthorizationKey->get($challenge->token);
if(!$result) {
throw new Exception\HTTPAuthorizationInvalid(
'HTTP challenge for "' . $domain . '"": ' .
$domain . '/.well-known/acme-challenge/' . $challenge->token .
' tested, found invalid. CURL response: ' . var_export($response, true)
);
}
return true;
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace LE_ACME2\Cache;
defined('ABSPATH') or die();
use LE_ACME2\AbstractKeyValuable;
abstract class AbstractKeyValuableCache {
protected function __construct() {}
protected function _getObjectIdentifier(AbstractKeyValuable $object) : string {
return $object->getKeyDirectoryPath();
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace LE_ACME2\Cache;
defined('ABSPATH') or die();
use LE_ACME2\Account;
use LE_ACME2\Connector;
use LE_ACME2\Request;
use LE_ACME2\Response;
use LE_ACME2\Exception;
use LE_ACME2\Utilities;
use LE_ACME2\SingletonTrait;
class AccountResponse extends AbstractKeyValuableCache {
use SingletonTrait;
private const _FILE = 'CacheResponse';
private const _DEPRECATED_FILE = 'DirectoryNewAccountResponse';
private $_responses = [];
/**
* @param Account $account
* @return Response\Account\AbstractAccount
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function get(Account $account): Response\Account\AbstractAccount {
$accountIdentifier = $this->_getObjectIdentifier($account);
if(array_key_exists($accountIdentifier, $this->_responses)) {
return $this->_responses[ $accountIdentifier ];
}
$this->_responses[ $accountIdentifier ] = null;
$cacheFile = $account->getKeyDirectoryPath() . self::_FILE;
$deprecatedCacheFile = $account->getKeyDirectoryPath() . self::_DEPRECATED_FILE;
if(file_exists($deprecatedCacheFile) && !file_exists($cacheFile)) {
rename($deprecatedCacheFile, $cacheFile);
}
if(file_exists($cacheFile) && filemtime($cacheFile) > strtotime('-7 days')) {
$rawResponse = Connector\RawResponse::getFromString(file_get_contents($cacheFile));
$response = new Response\Account\Create($rawResponse);
$this->_responses[ $accountIdentifier ] = $response;
Utilities\Logger::getInstance()->add(
Utilities\Logger::LEVEL_DEBUG,
get_class() . '::' . __FUNCTION__ . ' response from cache'
);
return $response;
}
$request = new Request\Account\Get($account);
$response = $request->getResponse();
$this->set($account, $response);
return $response;
}
public function set(Account $account, Response\Account\AbstractAccount $response = null) : void {
$accountIdentifier = $this->_getObjectIdentifier($account);
$filePath = $account->getKeyDirectoryPath() . self::_FILE;
if($response === null) {
unset($this->_responses[$accountIdentifier]);
if(file_exists($filePath)) {
unlink($filePath);
}
return;
}
$this->_responses[$accountIdentifier] = $response;
file_put_contents($filePath, $response->getRaw()->toString());
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace LE_ACME2\Cache;
defined('ABSPATH') or die();
use LE_ACME2\Connector;
use LE_ACME2\Account;
use LE_ACME2\SingletonTrait;
use LE_ACME2\Exception;
use LE_ACME2\Request;
use LE_ACME2\Response;
class DirectoryResponse {
use SingletonTrait;
private const _FILE = 'DirectoryResponse';
private function __construct() {}
private $_responses = [];
private $_index = 0;
/**
* @return Response\GetDirectory
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function get() : Response\GetDirectory {
if(array_key_exists($this->_index, $this->_responses)) {
return $this->_responses[$this->_index];
}
$this->_responses[$this->_index] = null;
$cacheFile = Account::getCommonKeyDirectoryPath() . self::_FILE;
if(file_exists($cacheFile) && filemtime($cacheFile) > strtotime('-2 days')) {
$rawResponse = Connector\RawResponse::getFromString(file_get_contents($cacheFile));
try {
return $this->_responses[$this->_index] = new Response\GetDirectory($rawResponse);
} catch(Exception\AbstractException $e) {
unlink($cacheFile);
}
}
$request = new Request\GetDirectory();
$response = $request->getResponse();
$this->set($response);
return $response;
}
public function set(Response\GetDirectory $response) : void {
$cacheFile = Account::getCommonKeyDirectoryPath() . self::_FILE;
$this->_responses[$this->_index] = $response;
file_put_contents($cacheFile, $response->getRaw()->toString());
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace LE_ACME2\Cache;
defined('ABSPATH') or die();
use LE_ACME2\SingletonTrait;
use LE_ACME2\Exception;
use LE_ACME2\Request;
use LE_ACME2\Response;
class NewNonceResponse {
use SingletonTrait;
private function __construct() {}
private $_responses = [];
private $_index = 0;
/**
* @return Response\GetNewNonce
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function get() : Response\GetNewNonce {
if(array_key_exists($this->_index, $this->_responses)) {
return $this->_responses[$this->_index];
}
$this->_responses[$this->_index] = null;
$request = new Request\GetNewNonce();
$response = $request->getResponse();
$this->set($response);
return $response;
}
public function set(Response\GetNewNonce $response) : void {
$this->_responses[$this->_index] = $response;
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace LE_ACME2\Cache;
defined('ABSPATH') or die();
use LE_ACME2\Connector;
use LE_ACME2\Order;
use LE_ACME2\Request;
use LE_ACME2\Response;
use LE_ACME2\Exception;
use LE_ACME2\Utilities;
use LE_ACME2\SingletonTrait;
class OrderResponse extends AbstractKeyValuableCache {
use SingletonTrait;
private const _FILE = 'CacheResponse';
private const _DEPRECATED_FILE = 'DirectoryNewOrderResponse';
private $_responses = [];
public function exists(Order $order) : bool {
$cacheFile = $order->getKeyDirectoryPath() . self::_FILE;
$deprecatedCacheFile = $order->getKeyDirectoryPath() . self::_DEPRECATED_FILE;
return file_exists($cacheFile) || file_exists($deprecatedCacheFile);
}
/**
* @param Order $order
* @return Response\Order\AbstractOrder
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function get(Order $order): Response\Order\AbstractOrder {
$accountIdentifier = $this->_getObjectIdentifier($order->getAccount());
$orderIdentifier = $this->_getObjectIdentifier($order);
if(!isset($this->_responses[$accountIdentifier])) {
$this->_responses[$accountIdentifier] = [];
}
if(array_key_exists($orderIdentifier, $this->_responses[$accountIdentifier])) {
return $this->_responses[ $accountIdentifier ][ $orderIdentifier ];
}
$this->_responses[ $accountIdentifier ][ $orderIdentifier ] = null;
$cacheFile = $order->getKeyDirectoryPath() . self::_FILE;
$deprecatedCacheFile = $order->getKeyDirectoryPath() . self::_DEPRECATED_FILE;
if(file_exists($deprecatedCacheFile) && !file_exists($cacheFile)) {
rename($deprecatedCacheFile, $cacheFile);
}
if(file_exists($cacheFile)) {
$rawResponse = Connector\RawResponse::getFromString(file_get_contents($cacheFile));
$response = new Response\Order\Create($rawResponse);
if(
$response->getStatus() != Response\Order\AbstractOrder::STATUS_VALID
) {
Utilities\Logger::getInstance()->add(
Utilities\Logger::LEVEL_DEBUG,
get_class() . '::' . __FUNCTION__ . ' (cache did not satisfy, status "' . $response->getStatus() . '")'
);
$request = new Request\Order\Get($order, $response);
$response = $request->getResponse();
$this->set($order, $response);
return $response;
}
Utilities\Logger::getInstance()->add(
Utilities\Logger::LEVEL_DEBUG,
get_class() . '::' . __FUNCTION__ . ' (from cache, status "' . $response->getStatus() . '")'
);
$this->_responses[$accountIdentifier][$orderIdentifier] = $response;
return $response;
}
throw new \RuntimeException(
self::_FILE . ' could not be found for order: ' .
'- Path: ' . $order->getKeyDirectoryPath() . PHP_EOL .
'- Subjects: ' . var_export($order->getSubjects(), true) . PHP_EOL
);
}
public function set(Order $order, Response\Order\AbstractOrder $response = null) : void {
$accountIdentifier = $this->_getObjectIdentifier($order->getAccount());
$orderIdentifier = $this->_getObjectIdentifier($order);
$filePath = $order->getKeyDirectoryPath() . self::_FILE;
if($response === null) {
unset($this->_responses[$accountIdentifier][$orderIdentifier]);
if(file_exists($filePath)) {
unlink($filePath);
}
return;
}
$this->_responses[$accountIdentifier][$orderIdentifier] = $response;
file_put_contents($filePath, $response->getRaw()->toString());
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace LE_ACME2\Connector;
defined('ABSPATH') or die();
use LE_ACME2\Request;
use LE_ACME2\Response;
use LE_ACME2\SingletonTrait;
use LE_ACME2\Cache;
use LE_ACME2\Utilities;
use LE_ACME2\Exception;
class Connector {
use SingletonTrait;
const METHOD_GET = 'GET';
const METHOD_HEAD = 'HEAD';
const METHOD_POST = 'POST';
private function __construct() {}
protected $_baseURL = 'https://acme-v02.api.letsencrypt.org';
protected $_stagingBaseURL = 'https://acme-staging-v02.api.letsencrypt.org';
protected $_useStagingServer = true;
public function useStagingServer(bool $useStagingServer) {
$this->_useStagingServer = $useStagingServer;
}
public function isUsingStagingServer() : bool {
return $this->_useStagingServer;
}
public function getBaseURL() : string {
return $this->_useStagingServer ? $this->_stagingBaseURL : $this->_baseURL;
}
/**
* Makes a Curl request.
*
* @param string $method The HTTP method to use. Accepting GET, POST and HEAD requests.
* @param string $url The URL to make the request to.
* @param string $data The body to attach to a POST request. Expected as a JSON encoded string.
*
* @return RawResponse
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function request(string $method, string $url, string $data = null) : RawResponse {
Utilities\Logger::getInstance()->add(Utilities\Logger::LEVEL_INFO, 'will request from ' . $url, $data);
$handle = curl_init();
$headers = array(
'Accept: application/json',
'Content-Type: ' . ($method == self::METHOD_POST ? 'application/jose+json' : 'application/json') // ACME draft-10, section 6.2
);
curl_setopt($handle, CURLOPT_URL, $url);
curl_setopt($handle, CURLOPT_HTTPHEADER, $headers);
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($handle, CURLOPT_HEADER, true);
switch ($method) {
case self::METHOD_GET:
break;
case self::METHOD_POST:
curl_setopt($handle, CURLOPT_POST, true);
curl_setopt($handle, CURLOPT_POSTFIELDS, $data);
break;
case self::METHOD_HEAD:
curl_setopt($handle, CURLOPT_CUSTOMREQUEST, 'HEAD');
curl_setopt($handle, CURLOPT_NOBODY, true);
break;
default:
throw new \RuntimeException('HTTP request ' . $method . ' not supported.');
break;
}
$response = curl_exec($handle);
if(curl_errno($handle)) {
throw new \RuntimeException('Curl: ' . curl_error($handle));
}
$header_size = curl_getinfo($handle, CURLINFO_HEADER_SIZE);
$rawResponse = new RawResponse();
$rawResponse->init($method, $url, $response, $header_size);
Utilities\Logger::getInstance()->add(Utilities\Logger::LEVEL_INFO, self::class . ': response received', $rawResponse);
try {
$getNewNonceResponse = new Response\GetNewNonce($rawResponse);
Cache\NewNonceResponse::getInstance()->set($getNewNonceResponse);
} catch(Exception\InvalidResponse $e) {
if($method == self::METHOD_POST) {
$request = new Request\GetNewNonce();
Cache\NewNonceResponse::getInstance()->set($request->getResponse());
}
}
return $rawResponse;
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace LE_ACME2\Connector;
defined('ABSPATH') or die();
class RawResponse {
/** @var string */
public $request;
/** @var array */
public $header;
/** @var array|string */
public $body;
public function init(string $method, string $url, string $response, int $headerSize) {
$header = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
$body_json = json_decode($body, true);
$this->request = $method . ' ' . $url;
$this->header = array_map(function($line) {
return trim($line);
}, explode("\n", $header));
$this->body = $body_json === null ? $body : $body_json;
}
public function toString() : string {
return serialize([
'request' => $this->request,
'header' => $this->header,
'body' => $this->body,
]);
}
public static function getFromString(string $string) : self {
$array = unserialize($string);
$rawResponse = new self();
$rawResponse->request = $array['request'];
$rawResponse->header = $array['header'];
$rawResponse->body = $array['body'];
return $rawResponse;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace LE_ACME2\Exception;
defined('ABSPATH') or die();
use LE_ACME2\Utilities;
abstract class AbstractException extends \Exception {
public function __construct(string $message) {
Utilities\Logger::getInstance()->add(
Utilities\Logger::LEVEL_DEBUG,
'Exception "' . get_called_class() . '" thrown '
);
parent::__construct($message);
}
}

View File

@@ -0,0 +1,6 @@
<?php
namespace LE_ACME2\Exception;
defined('ABSPATH') or die();
class AuthorizationInvalid extends AbstractException {}

View File

@@ -0,0 +1,6 @@
<?php
namespace LE_ACME2\Exception;
defined('ABSPATH') or die();
class DNSAuthorizationInvalid extends AuthorizationInvalid {}

View File

@@ -0,0 +1,11 @@
<?php
namespace LE_ACME2\Exception;
defined('ABSPATH') or die();
class ExpiredAuthorization extends AbstractException {
public function __construct() {
parent::__construct("Expired authorization received");
}
}

View File

@@ -0,0 +1,6 @@
<?php
namespace LE_ACME2\Exception;
defined('ABSPATH') or die();
class HTTPAuthorizationInvalid extends AuthorizationInvalid {}

View File

@@ -0,0 +1,40 @@
<?php
namespace LE_ACME2\Exception;
defined('ABSPATH') or die();
use LE_ACME2\Connector\RawResponse;
class InvalidResponse extends AbstractException {
private $_rawResponse;
private $_responseStatus;
public function __construct(RawResponse $rawResponse, string $responseStatus = null) {
$this->_rawResponse = $rawResponse;
$this->_responseStatus = $responseStatus;
if($responseStatus === '') {
$responseStatus = 'Unknown response status';
}
if(isset($this->_rawResponse->body['type'])) {
$responseStatus = $this->_rawResponse->body['type'];
}
if(isset($this->_rawResponse->body['detail'])) {
$responseStatus .= ' - ' . $this->_rawResponse->body['detail'];
}
parent::__construct('Invalid response received: ' . $responseStatus);
}
public function getRawResponse() : RawResponse {
return $this->_rawResponse;
}
public function getResponseStatus() : ?string {
return $this->_responseStatus;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace LE_ACME2\Exception;
defined('ABSPATH') or die();
class OpenSSLException extends AbstractException {
public function __construct(string $function) {
$errors = [];
while(($error = openssl_error_string()) !== false) {
$errors[] = $error;
}
parent::__construct(
$function . ' failed - error messages: ' . var_export($errors, true)
);
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace LE_ACME2\Exception;
defined('ABSPATH') or die();
class RateLimitReached extends AbstractException {
public function __construct(string $request, string $detail) {
parent::__construct(
"Invalid response received for request (" . $request . "): " .
"rate limit reached - " . $detail
);
}
}

View File

@@ -0,0 +1,6 @@
<?php
namespace LE_ACME2\Exception;
defined('ABSPATH') or die();
class StatusInvalid extends AbstractException {}

View File

@@ -0,0 +1,438 @@
<?php
namespace LE_ACME2;
defined('ABSPATH') or die();
use LE_ACME2\Request;
use LE_ACME2\Response;
use LE_ACME2\Cache;
use LE_ACME2\Authorizer;
use LE_ACME2\Exception;
use LE_ACME2\Utilities;
class Order extends AbstractKeyValuable {
const CHALLENGE_TYPE_HTTP = 'http-01';
const CHALLENGE_TYPE_DNS = 'dns-01';
/**
* @deprecated
* @param $directoryPath
*/
public static function setHTTPAuthorizationDirectoryPath(string $directoryPath) {
Authorizer\HTTP::setDirectoryPath($directoryPath);
}
CONST IDENTRUST_ISSUER_CN = 'DST Root CA X3';
/** @var string|null $_preferredChain */
private static $_preferredChain = null;
public static function setPreferredChain(string $issuerCN = null) {
self::$_preferredChain = $issuerCN;
}
protected $_account;
protected $_subjects;
public function __construct(Account $account, array $subjects) {
array_map(function($subject) {
if(preg_match_all('~(\*\.)~', $subject) > 1)
throw new \RuntimeException('Cannot create orders with multiple wildcards in one domain.');
}, $subjects);
$this->_account = $account;
$this->_subjects = $subjects;
$this->_identifier = $this->_getAccountIdentifier($account) . DIRECTORY_SEPARATOR .
'order_' . md5(implode('|', $subjects));
Utilities\Logger::getInstance()->add(
Utilities\Logger::LEVEL_INFO,
get_class() . '::' . __FUNCTION__ . ' "' . implode(':', $this->getSubjects()) . '"'
);
Utilities\Logger::getInstance()->add(
Utilities\Logger::LEVEL_DEBUG,
get_class() . '::' . __FUNCTION__ . ' path: ' . $this->getKeyDirectoryPath()
);
}
public function getAccount() : Account {
return $this->_account;
}
public function getSubjects() : array {
return $this->_subjects;
}
/**
* @param Account $account
* @param array $subjects
* @param string $keyType
* @return Order
* @throws Exception\AbstractException
*/
public static function create(Account $account, array $subjects, string $keyType = self::KEY_TYPE_RSA) : Order {
$order = new self($account, $subjects);
return $order->_create($keyType, false);
}
/**
* @param $keyType
* @param bool $ignoreIfKeysExist
* @return Order
* @throws Exception\AbstractException
*/
protected function _create(string $keyType, bool $ignoreIfKeysExist = false) : Order {
$this->_initKeyDirectory($keyType, $ignoreIfKeysExist);
$request = new Request\Order\Create($this);
try {
$response = $request->getResponse();
Cache\OrderResponse::getInstance()->set($this, $response);
return $this;
} catch(Exception\AbstractException $e) {
$this->_clearKeyDirectory();
throw $e;
}
}
public static function exists(Account $account, array $subjects) : bool {
$order = new self($account, $subjects);
return Cache\OrderResponse::getInstance()->exists($order);
}
public static function get(Account $account, array $subjects) : Order {
$order = new self($account, $subjects);
if(!self::exists($account, $subjects))
throw new \RuntimeException('Order does not exist');
return $order;
}
/** @var Authorizer\AbstractAuthorizer|Authorizer\HTTP|null $_authorizer */
protected $_authorizer = null;
/**
* @param $type
* @return Authorizer\AbstractAuthorizer|Authorizer\HTTP|null
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
* @throws Exception\ExpiredAuthorization
*/
protected function _getAuthorizer(string $type) : Authorizer\AbstractAuthorizer {
if($this->_authorizer === null) {
if($type == self::CHALLENGE_TYPE_HTTP) {
$this->_authorizer = new Authorizer\HTTP($this->_account, $this);
} else if($type == self::CHALLENGE_TYPE_DNS) {
$this->_authorizer = new Authorizer\DNS($this->_account, $this);
} else {
throw new \RuntimeException('Challenge type not implemented');
}
}
return $this->_authorizer;
}
/**
* The Authorization has expired, so we clean the complete order to restart again on the next call
*/
protected function _clearAfterExpiredAuthorization() {
Utilities\Logger::getInstance()->add(
Utilities\Logger::LEVEL_INFO,
get_class() . '::' . __FUNCTION__ . ' "Will clear after expired authorization'
);
$this->clear();
}
public function clear() {
Cache\OrderResponse::getInstance()->set($this, null);
$this->_clearKeyDirectory();
}
/**
* @return bool
* @param $type
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function shouldStartAuthorization(string $type) : bool {
try {
return $this->_getAuthorizer($type)->shouldStartAuthorization();
} catch(Exception\ExpiredAuthorization $e) {
$this->_clearAfterExpiredAuthorization();
return false;
}
}
/**
* @param $type
* @return bool
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
* @throws Exception\AuthorizationInvalid
*/
public function authorize(string $type) : bool {
try {
$authorizer = $this->_getAuthorizer($type);
$authorizer->progress();
return $authorizer->hasFinished();
} catch(Exception\ExpiredAuthorization $e) {
$this->_clearAfterExpiredAuthorization();
return false;
}
}
/**
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function finalize() {
if(!is_object($this->_authorizer) || !$this->_authorizer->hasFinished()) {
throw new \RuntimeException('Not all challenges are valid. Please check result of authorize() first!');
}
Utilities\Logger::getInstance()->add(
Utilities\Logger::LEVEL_INFO,
get_class() . '::' . __FUNCTION__ . ' "Will finalize'
);
$orderResponse = Cache\OrderResponse::getInstance()->get($this);
if(
$orderResponse->getStatus() == Response\Order\AbstractOrder::STATUS_PENDING /* DEPRECATED AFTER JULI 5TH 2018 */ ||
$orderResponse->getStatus() == Response\Order\AbstractOrder::STATUS_READY // ACME draft-12 Section 7.1.6
) {
$request = new Request\Order\Finalize($this, $orderResponse);
$orderResponse = $request->getResponse();
Cache\OrderResponse::getInstance()->set($this, $orderResponse);
}
if($orderResponse->getStatus() == Response\Order\AbstractOrder::STATUS_VALID) {
$request = new Request\Order\GetCertificate($this, $orderResponse);
$response = $request->getResponse();
$certificate = $response->getCertificate();
$intermediate = $response->getIntermediate();
//$certificateInfo = openssl_x509_parse($certificate);
//$certificateValidToTimeTimestamp = $certificateInfo['validTo_time_t'];
$intermediateInfo = openssl_x509_parse($intermediate);
if(self::$_preferredChain !== null) {
Utilities\Logger::getInstance()->add(
Utilities\Logger::LEVEL_INFO,
'Preferred chain is set: ' . self::$_preferredChain
);
}
$found = false;
if(self::$_preferredChain !== null && $intermediateInfo['issuer']['CN'] != self::$_preferredChain) {
Utilities\Logger::getInstance()->add(
Utilities\Logger::LEVEL_INFO,
'Default certificate does not satisfy preferred chain, trying to fetch alternative'
);
foreach($response->getAlternativeLinks() as $link) {
$request = new Request\Order\GetCertificate($this, $orderResponse, $link);
$response = $request->getResponse();
$alternativeCertificate = $response->getCertificate();
$alternativeIntermediate = $response->getIntermediate();
$intermediateInfo = openssl_x509_parse($intermediate);
if($intermediateInfo['issuer']['CN'] != self::$_preferredChain) {
continue;
}
$found = true;
$certificate = $alternativeCertificate;
$intermediate = $alternativeIntermediate;
break;
}
if(!$found) {
Utilities\Logger::getInstance()->add(
Utilities\Logger::LEVEL_INFO,
'Preferred chain could not be satisfied, returning default chain'
);
}
}
$this->_saveCertificate($certificate, $intermediate);
}
}
private function _saveCertificate(string $certificate, string $intermediate) : void {
$certificateInfo = openssl_x509_parse($certificate);
$certificateValidToTimeTimestamp = $certificateInfo['validTo_time_t'];
$path = $this->getKeyDirectoryPath() . self::BUNDLE_DIRECTORY_PREFIX . $certificateValidToTimeTimestamp . DIRECTORY_SEPARATOR;
mkdir($path);
rename($this->getKeyDirectoryPath() . 'private.pem', $path . 'private.pem');
file_put_contents($path . 'certificate.crt', $certificate);
file_put_contents($path . 'intermediate.pem', $intermediate);
Utilities\Logger::getInstance()->add(Utilities\Logger::LEVEL_INFO, 'Certificate received');
}
const BUNDLE_DIRECTORY_PREFIX = 'bundle_';
protected function _getLatestCertificateDirectory() : ?string {
$files = scandir($this->getKeyDirectoryPath(), SORT_NUMERIC | SORT_DESC);
foreach($files as $file) {
if(
substr($file, 0, strlen(self::BUNDLE_DIRECTORY_PREFIX)) == self::BUNDLE_DIRECTORY_PREFIX &&
is_dir($this->getKeyDirectoryPath() . $file)
) {
return $file;
}
}
return null;
}
public function isCertificateBundleAvailable() : bool {
return $this->_getLatestCertificateDirectory() !== NULL;
}
public function getCertificateBundle() : Struct\CertificateBundle {
if(!$this->isCertificateBundleAvailable()) {
throw new \RuntimeException('There is no certificate available');
}
$certificatePath = $this->getKeyDirectoryPath() . $this->_getLatestCertificateDirectory();
return new Struct\CertificateBundle(
$certificatePath . DIRECTORY_SEPARATOR,
'private.pem',
'certificate.crt',
'intermediate.pem',
self::_getExpireTimeFromCertificateDirectoryPath($certificatePath)
);
}
/**
* @param string $keyType
* @param int|null $renewBefore Unix timestamp
* @throws Exception\AbstractException
*/
public function enableAutoRenewal($keyType = self::KEY_TYPE_RSA, int $renewBefore = null) {
if($keyType === null) {
$keyType = self::KEY_TYPE_RSA;
}
if(!$this->isCertificateBundleAvailable()) {
throw new \RuntimeException('There is no certificate available');
}
$orderResponse = Cache\OrderResponse::getInstance()->get($this);
if(
$orderResponse === null ||
$orderResponse->getStatus() != Response\Order\AbstractOrder::STATUS_VALID
) {
return;
}
Utilities\Logger::getInstance()->add(Utilities\Logger::LEVEL_DEBUG,'Auto renewal triggered');
$directory = $this->_getLatestCertificateDirectory();
$expireTime = self::_getExpireTimeFromCertificateDirectoryPath($directory);
if($renewBefore === null) {
$renewBefore = strtotime('-30 days', $expireTime);
}
if($renewBefore < time()) {
Utilities\Logger::getInstance()->add(Utilities\Logger::LEVEL_INFO,'Auto renewal: Will recreate order');
$this->_create($keyType, true);
}
}
/**
* @param int $reason The reason to revoke the LetsEncrypt Order instance certificate.
* Possible reasons can be found in section 5.3.1 of RFC5280.
* @return bool
* @throws Exception\RateLimitReached
*/
public function revokeCertificate(int $reason = 0) : bool {
if(!$this->isCertificateBundleAvailable()) {
throw new \RuntimeException('There is no certificate available to revoke');
}
$bundle = $this->getCertificateBundle();
$request = new Request\Order\RevokeCertificate($bundle, $reason);
try {
/* $response = */ $request->getResponse();
rename(
$this->getKeyDirectoryPath(),
$this->_getKeyDirectoryPath('-revoked-' . microtime(true))
);
return true;
} catch(Exception\InvalidResponse $e) {
return false;
}
}
protected static function _getExpireTimeFromCertificateDirectoryPath(string $path) {
$stringPosition = strrpos($path, self::BUNDLE_DIRECTORY_PREFIX);
if($stringPosition === false) {
throw new \RuntimeException('ExpireTime not found in' . $path);
}
$expireTime = substr($path, $stringPosition + strlen(self::BUNDLE_DIRECTORY_PREFIX));
if(
!is_numeric($expireTime) ||
$expireTime < strtotime('-10 years') ||
$expireTime > strtotime('+10 years')
) {
throw new \RuntimeException('Unexpected expireTime: ' . $expireTime);
}
return $expireTime;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace LE_ACME2\Request;
defined('ABSPATH') or die();
use LE_ACME2\Response\AbstractResponse;
use LE_ACME2\Exception;
abstract class AbstractRequest {
/**
* @return AbstractResponse
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
abstract public function getResponse() : AbstractResponse;
protected function _buildContactPayload(string $email) : array {
$result = [
'mailto:' . $email
];
return $result;
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace LE_ACME2\Request\Account;
defined('ABSPATH') or die();
use LE_ACME2\Request\AbstractRequest;
use LE_ACME2\Connector;
use LE_ACME2\Cache;
use LE_ACME2\Utilities;
use LE_ACME2\Exception;
use LE_ACME2\Account;
abstract class AbstractLocation extends AbstractRequest {
protected $_account;
public function __construct(Account $account) {
$this->_account = $account;
}
/**
* @return Connector\RawResponse
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
protected function _getRawResponse() : Connector\RawResponse {
$payload = $this->_getPayload();
if(count($payload) == 0) {
$payload['rand-' . rand(100000, 1000000)] = 1;
}
$kid = Utilities\RequestSigner::KID(
$payload,
Cache\AccountResponse::getInstance()->get($this->_account)->getLocation(),
Cache\AccountResponse::getInstance()->get($this->_account)->getLocation(),
Cache\NewNonceResponse::getInstance()->get()->getNonce(),
$this->_account->getKeyDirectoryPath()
);
$result = Connector\Connector::getInstance()->request(
Connector\Connector::METHOD_POST,
Cache\AccountResponse::getInstance()->get($this->_account)->getLocation(),
$kid
);
return $result;
}
abstract protected function _getPayload() : array;
}

View File

@@ -0,0 +1,84 @@
<?php
namespace LE_ACME2\Request\Account;
defined('ABSPATH') or die();
use LE_ACME2\Request\AbstractRequest;
use LE_ACME2\Response;
use LE_ACME2\Connector;
use LE_ACME2\Cache;
use LE_ACME2\Utilities;
use LE_ACME2\Exception;
use LE_ACME2\Account;
class ChangeKeys extends AbstractRequest {
protected $_account;
public function __construct(Account $account) {
$this->_account = $account;
}
/**
* @return Response\AbstractResponse|Response\Account\ChangeKeys
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function getResponse() : Response\AbstractResponse {
$currentPrivateKey = openssl_pkey_get_private(
file_get_contents($this->_account->getKeyDirectoryPath() . 'private.pem')
);
$currentPrivateKeyDetails = openssl_pkey_get_details($currentPrivateKey);
/**
* draft-13 Section 7.3.6
* "newKey" is deprecated after August 23rd 2018
*/
$newPrivateKey = openssl_pkey_get_private(
file_get_contents($this->_account->getKeyDirectoryPath() . 'private-replacement.pem')
);
$newPrivateKeyDetails = openssl_pkey_get_details($newPrivateKey);
$innerPayload = [
'account' => Cache\AccountResponse::getInstance()->get($this->_account)->getLocation(),
'oldKey' => [
"kty" => "RSA",
"n" => Utilities\Base64::UrlSafeEncode($currentPrivateKeyDetails["rsa"]["n"]),
"e" => Utilities\Base64::UrlSafeEncode($currentPrivateKeyDetails["rsa"]["e"])
],
'newKey' => [
"kty" => "RSA",
"n" => Utilities\Base64::UrlSafeEncode($newPrivateKeyDetails["rsa"]["n"]),
"e" => Utilities\Base64::UrlSafeEncode($newPrivateKeyDetails["rsa"]["e"])
]
];
$outerPayload = Utilities\RequestSigner::JWK(
$innerPayload,
Cache\DirectoryResponse::getInstance()->get()->getKeyChange(),
Cache\NewNonceResponse::getInstance()->get()->getNonce(),
$this->_account->getKeyDirectoryPath(),
'private-replacement.pem'
);
$data = Utilities\RequestSigner::KID(
$outerPayload,
Cache\AccountResponse::getInstance()->get($this->_account)->getLocation(),
Cache\DirectoryResponse::getInstance()->get()->getKeyChange(),
Cache\NewNonceResponse::getInstance()->get()->getNonce(),
$this->_account->getKeyDirectoryPath(),
'private.pem'
);
$result = Connector\Connector::getInstance()->request(
Connector\Connector::METHOD_POST,
Cache\DirectoryResponse::getInstance()->get()->getKeyChange(),
$data
);
return new Response\Account\ChangeKeys($result);
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace LE_ACME2\Request\Account;
defined('ABSPATH') or die();
use LE_ACME2\Request\AbstractRequest;
use LE_ACME2\Response;
use LE_ACME2\Connector;
use LE_ACME2\Cache;
use LE_ACME2\Utilities;
use LE_ACME2\Exception;
use LE_ACME2\Account;
class Create extends AbstractRequest {
protected $_account;
public function __construct(Account $account) {
$this->_account = $account;
}
/**
* @return Response\AbstractResponse|Response\Account\Create
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function getResponse() : Response\AbstractResponse {
$payload = [
'contact' => $this->_buildContactPayload($this->_account->getEmail()),
'termsOfServiceAgreed' => true,
];
$jwk = Utilities\RequestSigner::JWKString(
$payload,
Cache\DirectoryResponse::getInstance()->get()->getNewAccount(),
Cache\NewNonceResponse::getInstance()->get()->getNonce(),
$this->_account->getKeyDirectoryPath()
);
$result = Connector\Connector::getInstance()->request(
Connector\Connector::METHOD_POST,
Cache\DirectoryResponse::getInstance()->get()->getNewAccount(),
$jwk
);
return new Response\Account\Create($result);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace LE_ACME2\Request\Account;
defined('ABSPATH') or die();
use LE_ACME2\Response;
use LE_ACME2\Exception;
class Deactivate extends AbstractLocation {
protected function _getPayload() : array {
return [
'status' => 'deactivated',
];
}
/**
* @return Response\AbstractResponse|Response\Account\Deactivate
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function getResponse() : Response\AbstractResponse {
return new Response\Account\Deactivate($this->_getRawResponse());
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace LE_ACME2\Request\Account;
defined('ABSPATH') or die();
use LE_ACME2\Request\AbstractRequest;
use LE_ACME2\Response;
use LE_ACME2\Connector;
use LE_ACME2\Cache;
use LE_ACME2\Exception;
use LE_ACME2\Utilities;
use LE_ACME2\Account;
class Get extends AbstractRequest {
protected $_account;
public function __construct(Account $account) {
$this->_account = $account;
}
/**
* @return Response\AbstractResponse|Response\Account\Get
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function getResponse() : Response\AbstractResponse {
$payload = [
'onlyReturnExisting' => true,
];
$jwk = Utilities\RequestSigner::JWKString(
$payload,
Cache\DirectoryResponse::getInstance()->get()->getNewAccount(),
Cache\NewNonceResponse::getInstance()->get()->getNonce(),
$this->_account->getKeyDirectoryPath()
);
$result = Connector\Connector::getInstance()->request(
Connector\Connector::METHOD_POST,
Cache\DirectoryResponse::getInstance()->get()->getNewAccount(),
$jwk
);
return new Response\Account\Get($result);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace LE_ACME2\Request\Account;
defined('ABSPATH') or die();
use LE_ACME2\Response;
use LE_ACME2\Exception;
class GetData extends AbstractLocation {
protected function _getPayload() : array {
return [];
}
/**
* @return Response\AbstractResponse|Response\Account\GetData
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function getResponse() : Response\AbstractResponse {
return new Response\Account\GetData($this->_getRawResponse());
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace LE_ACME2\Request\Account;
defined('ABSPATH') or die();
use LE_ACME2\Response;
use LE_ACME2\Exception;
use LE_ACME2\Account;
class Update extends AbstractLocation {
protected $_newEmail;
public function __construct(Account $account, $newEmail) {
parent::__construct($account);
$this->_newEmail = $newEmail;
}
protected function _getPayload() : array {
return [
'contact' => $this->_buildContactPayload($this->_newEmail),
];
}
/**
* @return Response\AbstractResponse|Response\Account\Update
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function getResponse() : Response\AbstractResponse {
return new Response\Account\Update($this->_getRawResponse());
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace LE_ACME2\Request\Authorization;
defined('ABSPATH') or die();
use LE_ACME2\Request\AbstractRequest;
use LE_ACME2\Connector;
use LE_ACME2\Cache;
use LE_ACME2\Exception;
use LE_ACME2\Response;
use LE_ACME2\Utilities;
use LE_ACME2\Account;
class Get extends AbstractRequest {
protected $_account;
protected $_authorizationURL;
public function __construct(Account $account, string $authorizationURL) {
$this->_account = $account;
$this->_authorizationURL = $authorizationURL;
}
/**
* @return Response\AbstractResponse|Response\Authorization\Get
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
* @throws Exception\ExpiredAuthorization
*/
public function getResponse() : Response\AbstractResponse {
$kid = Utilities\RequestSigner::KID(
null,
Cache\AccountResponse::getInstance()->get($this->_account)->getLocation(),
$this->_authorizationURL,
Cache\NewNonceResponse::getInstance()->get()->getNonce(),
$this->_account->getKeyDirectoryPath()
);
$result = Connector\Connector::getInstance()->request(
Connector\Connector::METHOD_POST,
$this->_authorizationURL,
$kid
);
return new Response\Authorization\Get($result);
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace LE_ACME2\Request\Authorization;
defined('ABSPATH') or die();
use LE_ACME2\Request\AbstractRequest;
use LE_ACME2\Connector;
use LE_ACME2\Cache;
use LE_ACME2\Exception;
use LE_ACME2\Response;
use LE_ACME2\Struct\ChallengeAuthorizationKey;
use LE_ACME2\Utilities;
use LE_ACME2\Account;
use LE_ACME2\Order;
class Start extends AbstractRequest {
protected $_account;
protected $_order;
protected $_challenge;
public function __construct(Account $account, Order $order, Response\Authorization\Struct\Challenge $challenge) {
$this->_account = $account;
$this->_order = $order;
$this->_challenge = $challenge;
}
/**
* @return Response\AbstractResponse|Response\Authorization\Start
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
* @throws Exception\ExpiredAuthorization
*/
public function getResponse() : Response\AbstractResponse {
$payload = [
'keyAuthorization' => (new ChallengeAuthorizationKey($this->_account))->get($this->_challenge->token)
];
$kid = Utilities\RequestSigner::KID(
$payload,
Cache\AccountResponse::getInstance()->get($this->_account)->getLocation(),
$this->_challenge->url,
Cache\NewNonceResponse::getInstance()->get()->getNonce(),
$this->_account->getKeyDirectoryPath()
);
$result = Connector\Connector::getInstance()->request(
Connector\Connector::METHOD_POST,
$this->_challenge->url,
$kid
);
return new Response\Authorization\Start($result);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace LE_ACME2\Request;
defined('ABSPATH') or die();
use LE_ACME2\Response;
use LE_ACME2\Connector\Connector;
use LE_ACME2\Exception;
class GetDirectory extends AbstractRequest {
/**
* @return Response\AbstractResponse|Response\GetDirectory
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function getResponse() : Response\AbstractResponse {
$connector = Connector::getInstance();
$result = $connector->request(
Connector::METHOD_GET,
$connector->getBaseURL() . '/directory'
);
return new Response\GetDirectory($result);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace LE_ACME2\Request;
defined('ABSPATH') or die();
use LE_ACME2\Response;
use LE_ACME2\Connector;
use LE_ACME2\Cache;
use LE_ACME2\Exception;
class GetNewNonce extends AbstractRequest {
/**
* @return Response\AbstractResponse|Response\GetNewNonce
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function getResponse() : Response\AbstractResponse {
$result = Connector\Connector::getInstance()->request(
Connector\Connector::METHOD_HEAD,
Cache\DirectoryResponse::getInstance()->get()->getNewNonce()
);
return new Response\GetNewNonce($result);
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace LE_ACME2\Request\Order;
defined('ABSPATH') or die();
use LE_ACME2\Request\AbstractRequest;
use LE_ACME2\Response;
use LE_ACME2\Connector;
use LE_ACME2\Cache;
use LE_ACME2\Exception;
use LE_ACME2\Utilities;
use LE_ACME2\Order;
class Create extends AbstractRequest {
protected $_order;
public function __construct(Order $order) {
$this->_order = $order;
}
/**
* @return Response\AbstractResponse|Response\Order\Create
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function getResponse() : Response\AbstractResponse {
$identifiers = [];
foreach($this->_order->getSubjects() as $subject) {
$identifiers[] = [
'type' => 'dns',
'value' => $subject
];
}
$payload = [
'identifiers' => $identifiers,
'notBefore' => '',
'notAfter' => '',
];
$kid = Utilities\RequestSigner::KID(
$payload,
Cache\AccountResponse::getInstance()->get($this->_order->getAccount())->getLocation(),
Cache\DirectoryResponse::getInstance()->get()->getNewOrder(),
Cache\NewNonceResponse::getInstance()->get()->getNonce(),
$this->_order->getAccount()->getKeyDirectoryPath()
);
$result = Connector\Connector::getInstance()->request(
Connector\Connector::METHOD_POST,
Cache\DirectoryResponse::getInstance()->get()->getNewOrder(),
$kid
);
return new Response\Order\Create($result);
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace LE_ACME2\Request\Order;
defined('ABSPATH') or die();
use LE_ACME2\Request\AbstractRequest;
use LE_ACME2\Response;
use LE_ACME2\Connector;
use LE_ACME2\Cache;
use LE_ACME2\Exception;
use LE_ACME2\Utilities;
use LE_ACME2\Order;
class Finalize extends AbstractRequest {
protected $_order;
protected $_orderResponse;
public function __construct(Order $order, Response\Order\AbstractOrder $orderResponse) {
$this->_order = $order;
$this->_orderResponse = $orderResponse;
}
/**
* @return Response\AbstractResponse|Response\Order\Finalize
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function getResponse() : Response\AbstractResponse {
$csr = Utilities\Certificate::generateCSR($this->_order);
if(preg_match('~-----BEGIN\sCERTIFICATE\sREQUEST-----(.*)-----END\sCERTIFICATE\sREQUEST-----~s', $csr, $matches))
$csr = $matches[1];
$csr = trim(Utilities\Base64::UrlSafeEncode(base64_decode($csr)));
$payload = [
'csr' => $csr
];
$kid = Utilities\RequestSigner::KID(
$payload,
Cache\AccountResponse::getInstance()->get($this->_order->getAccount())->getLocation(),
$this->_orderResponse->getFinalize(),
Cache\NewNonceResponse::getInstance()->get()->getNonce(),
$this->_order->getAccount()->getKeyDirectoryPath()
);
$result = Connector\Connector::getInstance()->request(
Connector\Connector::METHOD_POST,
$this->_orderResponse->getFinalize(),
$kid
);
return new Response\Order\Finalize($result);
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace LE_ACME2\Request\Order;
defined('ABSPATH') or die();
use LE_ACME2\Request\AbstractRequest;
use LE_ACME2\Response;
use LE_ACME2\Connector;
use LE_ACME2\Cache;
use LE_ACME2\Exception;
use LE_ACME2\Utilities;
use LE_ACME2\Order;
class Get extends AbstractRequest {
protected $_order;
protected $_orderResponse;
public function __construct(Order $order, Response\Order\AbstractOrder $orderResponse) {
$this->_order = $order;
$this->_orderResponse = $orderResponse;
}
/**
* @return Response\AbstractResponse|Response\Order\Get
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function getResponse() : Response\AbstractResponse {
$kid = Utilities\RequestSigner::KID(
null,
Cache\AccountResponse::getInstance()->get($this->_order->getAccount())->getLocation(),
$this->_orderResponse->getLocation(),
Cache\NewNonceResponse::getInstance()->get()->getNonce(),
$this->_order->getAccount()->getKeyDirectoryPath()
);
$result = Connector\Connector::getInstance()->request(
Connector\Connector::METHOD_POST,
$this->_orderResponse->getLocation(),
$kid
);
return new Response\Order\Get($result, $this->_orderResponse->getLocation());
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace LE_ACME2\Request\Order;
defined('ABSPATH') or die();
use LE_ACME2\Order;
use LE_ACME2\Request\AbstractRequest;
use LE_ACME2\Response;
use LE_ACME2\Connector;
use LE_ACME2\Cache;
use LE_ACME2\Exception;
use LE_ACME2\Utilities;
class GetCertificate extends AbstractRequest {
protected $_order;
protected $_orderResponse;
private $_alternativeUrl = null;
public function __construct(Order $order, Response\Order\AbstractOrder $orderResponse,
string $alternativeUrl = null
) {
$this->_order = $order;
$this->_orderResponse = $orderResponse;
if($alternativeUrl !== null) {
$this->_alternativeUrl = $alternativeUrl;
}
}
/**
* @return Response\AbstractResponse|Response\Order\GetCertificate
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function getResponse() : Response\AbstractResponse {
$url = $this->_alternativeUrl === null ?
$this->_orderResponse->getCertificate() :
$this->_alternativeUrl;
$kid = Utilities\RequestSigner::KID(
null,
Cache\AccountResponse::getInstance()->get($this->_order->getAccount())->getLocation(),
$url,
Cache\NewNonceResponse::getInstance()->get()->getNonce(),
$this->_order->getAccount()->getKeyDirectoryPath()
);
$result = Connector\Connector::getInstance()->request(
Connector\Connector::METHOD_POST,
$url,
$kid
);
return new Response\Order\GetCertificate($result);
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace LE_ACME2\Request\Order;
defined('ABSPATH') or die();
use LE_ACME2\Response;
use LE_ACME2\Request\AbstractRequest;
use LE_ACME2\Connector;
use LE_ACME2\Cache;
use LE_ACME2\Exception;
use LE_ACME2\Struct;
use LE_ACME2\Utilities;
class RevokeCertificate extends AbstractRequest {
protected $_certificateBundle;
protected $_reason;
public function __construct(Struct\CertificateBundle $certificateBundle, $reason) {
$this->_certificateBundle = $certificateBundle;
$this->_reason = $reason;
}
/**
* @return Response\AbstractResponse|Response\Order\RevokeCertificate
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function getResponse() : Response\AbstractResponse {
$certificate = file_get_contents($this->_certificateBundle->path . $this->_certificateBundle->certificate);
preg_match('~-----BEGIN\sCERTIFICATE-----(.*)-----END\sCERTIFICATE-----~s', $certificate, $matches);
$certificate = trim(Utilities\Base64::UrlSafeEncode(base64_decode(trim($matches[1]))));
$payload = [
'certificate' => $certificate,
'reason' => $this->_reason
];
$jwk = Utilities\RequestSigner::JWKString(
$payload,
Cache\DirectoryResponse::getInstance()->get()->getRevokeCert(),
Cache\NewNonceResponse::getInstance()->get()->getNonce(),
$this->_certificateBundle->path,
$this->_certificateBundle->private
);
$result = Connector\Connector::getInstance()->request(
Connector\Connector::METHOD_POST,
Cache\DirectoryResponse::getInstance()->get()->getRevokeCert(),
$jwk
);
return new Response\Order\RevokeCertificate($result);
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace LE_ACME2\Response;
defined('ABSPATH') or die();
use LE_ACME2\Exception;
use LE_ACME2\Connector\RawResponse;
abstract class AbstractResponse {
protected $_raw = NULL;
protected $_pattern_header_location = '/^Location: (\S+)$/i';
/**
* AbstractResponse constructor.
*
* @param RawResponse $raw
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
*/
public function __construct(RawResponse $raw) {
$this->_raw = $raw;
if($this->_isRateLimitReached()) {
$detail = "";
if(isset($raw->body['type']) && $raw->body['type'] == 'urn:ietf:params:acme:error:rateLimited') {
$detail = $raw->body['detail'];
}
throw new Exception\RateLimitReached($raw->request, $detail);
}
$result = $this->_isValid();
if(!$result) {
$responseStatus = $this->_preg_match_headerLine('/^HTTP\/.* [0-9]{3,} /i');
throw new Exception\InvalidResponse(
$raw,
$responseStatus ? $responseStatus[1] : null
);
}
}
protected function _preg_match_headerLine(string $pattern) : ?array {
foreach($this->_raw->header as $line) {
if(preg_match($pattern, $line, $matches) === 1)
return $matches;
}
return null;
}
protected function _isRateLimitReached() : bool {
return $this->_preg_match_headerLine('/^HTTP\/.* 429/i') !== null;
}
protected function _isValid() : bool {
return $this->_preg_match_headerLine('/^HTTP\/.* 201/i') !== null || //Created
$this->_preg_match_headerLine('/^HTTP\/.* 200/i') !== null ||
$this->_preg_match_headerLine('/^HTTP\/.* 204/i') !== null;
}
public function getRaw() : RawResponse {
return $this->_raw;
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace LE_ACME2\Response\Account;
defined('ABSPATH') or die();
use LE_ACME2\Response\AbstractResponse;
abstract class AbstractAccount extends AbstractResponse {
const STATUS_VALID = 'valid';
public function getLocation() : string {
$matches = $this->_preg_match_headerLine($this->_pattern_header_location);
return trim($matches[1]);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace LE_ACME2\Response\Account;
defined('ABSPATH') or die();
use LE_ACME2\Response\AbstractResponse;
abstract class AbstractLocation extends AbstractResponse {
public function getKey() : string {
return $this->_raw->body['key'];
}
public function getContact() : string {
return $this->_raw->body['contact'];
}
public function getAgreement() : string {
return $this->_raw->body['agreement'];
}
public function getInitialIP() : string {
return $this->_raw->body['initialIp'];
}
public function getCreatedAt() : string {
return $this->_raw->body['createdAt'];
}
public function getStatus() : string {
return $this->_raw->body['status'];
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace LE_ACME2\Response\Account;
defined('ABSPATH') or die();
use LE_ACME2\Response\AbstractResponse;
class ChangeKeys extends AbstractResponse {}

View File

@@ -0,0 +1,6 @@
<?php
namespace LE_ACME2\Response\Account;
defined('ABSPATH') or die();
class Create extends AbstractAccount {}

View File

@@ -0,0 +1,6 @@
<?php
namespace LE_ACME2\Response\Account;
defined('ABSPATH') or die();
class Deactivate extends AbstractLocation {}

View File

@@ -0,0 +1,6 @@
<?php
namespace LE_ACME2\Response\Account;
defined('ABSPATH') or die();
class Get extends AbstractAccount {}

View File

@@ -0,0 +1,6 @@
<?php
namespace LE_ACME2\Response\Account;
defined('ABSPATH') or die();
class GetData extends AbstractLocation {}

View File

@@ -0,0 +1,6 @@
<?php
namespace LE_ACME2\Response\Account;
defined('ABSPATH') or die();
class Update extends AbstractLocation {}

View File

@@ -0,0 +1,36 @@
<?php
namespace LE_ACME2\Response\Authorization;
defined('ABSPATH') or die();
use LE_ACME2\Response\AbstractResponse;
use LE_ACME2\Connector\RawResponse;
use LE_ACME2\Exception;
class AbstractAuthorization extends AbstractResponse {
/**
* AbstractAuthorization constructor.
* @param RawResponse $raw
* @throws Exception\InvalidResponse
* @throws Exception\RateLimitReached
* @throws Exception\ExpiredAuthorization
*/
public function __construct(RawResponse $raw) {
parent::__construct($raw);
}
/**
* @return bool
* @throws Exception\ExpiredAuthorization
*/
protected function _isValid() : bool {
if($this->_preg_match_headerLine('/^HTTP\/.* 404/i') !== null) {
throw new Exception\ExpiredAuthorization();
}
return parent::_isValid();
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace LE_ACME2\Response\Authorization;
defined('ABSPATH') or die();
use LE_ACME2\Response\Authorization\Struct;
class Get extends AbstractAuthorization {
public function getIdentifier() : Struct\Identifier {
return new Struct\Identifier($this->_raw->body['identifier']['type'], $this->_raw->body['identifier']['value']);
}
public function getStatus() : string {
return $this->_raw->body['status'];
}
public function getExpires() : string {
return $this->_raw->body['expires'];
}
public function getChallenges() : array {
return $this->_raw->body['challenges'];
}
/**
* @param $type
* @return Struct\Challenge
*/
public function getChallenge(string $type) : Struct\Challenge {
foreach($this->getChallenges() as $challenge) {
if($type == $challenge['type'])
return new Struct\Challenge($challenge['type'], $challenge['status'], $challenge['url'], $challenge['token']);
}
throw new \RuntimeException('No challenge found with given type');
}
}

View File

@@ -0,0 +1,6 @@
<?php
namespace LE_ACME2\Response\Authorization;
defined('ABSPATH') or die();
class Start extends AbstractAuthorization {}

View File

@@ -0,0 +1,25 @@
<?php
namespace LE_ACME2\Response\Authorization\Struct;
defined('ABSPATH') or die();
class Challenge {
const STATUS_PROGRESSING = 'processing';
const STATUS_PENDING = 'pending';
const STATUS_VALID = 'valid';
const STATUS_INVALID = 'invalid';
public $type;
public $status;
public $url;
public $token;
public function __construct(string $type, string $status, string $url, string $token) {
$this->type = $type;
$this->status = $status;
$this->url = $url;
$this->token = $token;
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace LE_ACME2\Response\Authorization\Struct;
defined('ABSPATH') or die();
class Identifier {
public $type;
public $value;
public function __construct(string $type, string $value) {
$this->type = $type;
$this->value = $value;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace LE_ACME2\Response;
defined('ABSPATH') or die();
class GetDirectory extends AbstractResponse {
public function getKeyChange() : string {
return $this->_raw->body['keyChange'];
}
public function getNewAccount() : string {
return $this->_raw->body['newAccount'];
}
public function getNewNonce() : string {
return $this->_raw->body['newNonce'];
}
public function getNewOrder() : string {
return $this->_raw->body['newOrder'];
}
public function getRevokeCert() : string {
return $this->_raw->body['revokeCert'];
}
public function getTermsOfService() : string {
return $this->_raw->body['meta']['termsOfService'];
}
public function getWebsite() : string {
return $this->_raw->body['meta']['website'];
}
public function getCaaIdentities() : string {
return $this->_raw->body['meta']['caaIdentities'];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace LE_ACME2\Response;
defined('ABSPATH') or die();
class GetNewNonce extends AbstractResponse {
protected $_pattern = '/^Replay\-Nonce: (\S+)$/i';
protected function _isValid() : bool {
return $this->_preg_match_headerLine($this->_pattern) !== null;
}
public function getNonce() : string {
$matches = $this->_preg_match_headerLine($this->_pattern);
return trim($matches[1]);
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace LE_ACME2\Response\Order;
defined('ABSPATH') or die();
use LE_ACME2\Response\AbstractResponse;
use LE_ACME2\Exception;
abstract class AbstractOrder extends AbstractResponse {
const STATUS_PENDING = 'pending';
const STATUS_VALID = 'valid';
const STATUS_READY = 'ready';
const STATUS_INVALID = 'invalid';
public function getLocation() : string {
$matches = $this->_preg_match_headerLine($this->_pattern_header_location);
return trim($matches[1]);
}
public function getStatus() : string {
return $this->_raw->body['status'];
}
public function getExpires() : string {
return $this->_raw->body['expires'];
}
public function getIdentifiers() : array {
return $this->_raw->body['identifiers'];
}
public function getAuthorizations() : array {
return $this->_raw->body['authorizations'];
}
public function getFinalize() : string {
return $this->_raw->body['finalize'];
}
public function getCertificate() : string {
return $this->_raw->body['certificate'];
}
/**
* @return bool
* @throws Exception\StatusInvalid
*/
protected function _isValid(): bool {
if(!parent::_isValid()) {
return false;
}
if(
$this->getStatus() == AbstractOrder::STATUS_INVALID
) {
throw new Exception\StatusInvalid('Order has status "' . AbstractOrder::STATUS_INVALID . '"'.
'. Probably all authorizations have failed. ' . PHP_EOL .
'Please see: ' . $this->getLocation() . PHP_EOL .
'Continue by using $order->clear() after getting rid of the problem'
);
}
return true;
}
}

View File

@@ -0,0 +1,6 @@
<?php
namespace LE_ACME2\Response\Order;
defined('ABSPATH') or die();
class Create extends AbstractOrder {}

View File

@@ -0,0 +1,6 @@
<?php
namespace LE_ACME2\Response\Order;
defined('ABSPATH') or die();
class Finalize extends AbstractOrder {}

Some files were not shown because too many files have changed in this diff Show More