PluginProbe ʕ •ᴥ•ʔ
Really Simple Security – Simple and Performant Security (formerly Really Simple SSL) / 9.5.11
Really Simple Security – Simple and Performant Security (formerly Really Simple SSL) v9.5.11
9.5.11 9.5.10.1 9.5.10 trunk 9.4.0 9.4.1 9.4.2 9.4.3 9.5.0 9.5.0.1 9.5.0.2 9.5.1 9.5.2 9.5.2.2 9.5.2.3 9.5.3 9.5.3.1 9.5.3.2 9.5.4 9.5.5 9.5.6 9.5.7 9.5.8 9.5.9
really-simple-ssl / lets-encrypt / class-letsencrypt-handler.php
really-simple-ssl / lets-encrypt Last commit date
config 4 weeks ago integrations 4 weeks ago vendor 4 weeks ago class-le-restapi.php 4 weeks ago class-letsencrypt-handler.php 4 weeks ago composer.json 4 weeks ago cron.php 4 weeks ago download.php 4 weeks ago functions.php 4 weeks ago index.php 4 weeks ago letsencrypt.php 4 weeks ago
class-letsencrypt-handler.php
1915 lines
1 <?php
2 defined('ABSPATH') or die("you do not have access to this page!");
3
4 require_once rsssl_le_path . 'vendor/autoload.php';
5 require_once rsssl_path . 'lib/admin/class-encryption.php';
6 use RSSSL\lib\admin\Encryption;
7 use LE_ACME2\Account;
8 use LE_ACME2\Authorizer\AbstractDNSWriter;
9 use LE_ACME2\Authorizer\DNS;
10 use LE_ACME2\Authorizer\HTTP;
11 use LE_ACME2\Connector\Connector;
12 use LE_ACME2\Order;
13 use LE_ACME2\Utilities\Certificate;
14 use LE_ACME2\Utilities\Logger;
15 use RSSSL\Security\RSSSL_Htaccess_File_Manager;
16
17 class rsssl_letsencrypt_handler {
18 use Encryption;
19
20 private static $_this;
21 /**
22 * Account object
23 * @var bool|LE_ACME2\Account
24 */
25 public $account = false;
26 public $challenge_directory = false;
27 public $key_directory = false;
28 public $certs_directory = false;
29 public $subjects = array();
30
31 public $htaccess_file_manager;
32
33 public function __construct()
34 {
35 if ( isset( self::$_this ) ) {
36 wp_die();
37 }
38 $this->htaccess_file_manager = Rsssl_Htaccess_File_Manager::get_instance();
39 add_action('admin_init', array($this, 'upgrade'));
40
41 //loading of these hooks is stricter. The class can be used in the notices, which are needed on the generic dashboard
42 //These functionality is not needed on the dashboard, so should only be loaded in strict circumstances
43 if ( rsssl_letsencrypt_generation_allowed( true ) ) {
44 add_action( 'rsssl_after_save_field', array( $this, 'after_save_field' ), 10, 4 );
45 add_action( 'admin_init', array( $this, 'maybe_add_htaccess_exclude'));
46 add_action( 'admin_init', array( $this, 'maybe_create_htaccess_directories'));
47
48 $this->key_directory = $this->key_directory();
49 $this->challenge_directory = $this->challenge_directory();
50 $this->certs_directory = $this->certs_directory();
51
52 // Config the desired paths
53 if ( $this->key_directory ) {
54 Account::setCommonKeyDirectoryPath( $this->key_directory );
55 }
56
57 if ( $this->challenge_directory ) {
58 HTTP::setDirectoryPath( $this->challenge_directory );
59 }
60
61 // General configs
62 //$use_staging = defined('RSSSL_LE_DEBUG');
63 Connector::getInstance()->useStagingServer( false );
64 Logger::getInstance()->setDesiredLevel( Logger::LEVEL_DISABLED );
65
66 if ( !rsssl_get_option( 'disable_ocsp' ) ) {
67 Certificate::enableFeatureOCSPMustStaple();
68 }
69
70 Order::setPreferredChain('ISRG Root X1');
71 $this->subjects = $this->get_subjects();
72 }
73
74 self::$_this = $this;
75 }
76
77 public static function this() {
78 return self::$_this;
79 }
80
81 /**
82 * If we're on apache, add a line to the .htaccess so the acme challenge directory won't get blocked.
83 */
84 public function maybe_add_htaccess_exclude(): void {
85
86 if (!rsssl_user_can_manage()) {
87 return;
88 }
89
90 if ( !RSSSL()->server->uses_htaccess() ) {
91 return;
92 }
93
94 $htaccess_file = $this->htaccess_file_manager->htaccess_file_path;
95 if ( $this->htaccess_file_manager->validate_htaccess_file_path() === false ) {
96 return;
97 }
98
99 $htaccess = $this->htaccess_file_manager->get_htaccess_content();
100
101 // if the htaccess file is null we return early
102 if ( $htaccess === null ) {
103 return;
104 }
105
106 // We validate if the old markers are still present, and remove them if they are.
107 if ( $this->maybe_remove_old_markers( $htaccess ) ) {
108 $this->htaccess_file_manager->clear_legacy_rule('Really Simple Security LETS ENCRYPT');
109 }
110
111 $content_with_marker = [
112 'marker' => 'Really Simple Security LETS ENCRYPT',
113 'lines' => [
114 'RewriteRule ^.well-known/(.*)$ - [L]',
115 ],
116 ];
117
118 $this->htaccess_file_manager->write_rule($content_with_marker, 'created lines for acme challenge directory');
119
120 }
121
122 /**
123 * Validates if the old markers are still present, and removes them if they are.
124 */
125 private function maybe_remove_old_markers(string $htAccessContent ) :bool
126 {
127 //if it's already inserted, skip.
128 return strpos($htAccessContent, '#BEGIN Really Simple Security LETS ENCRYPT');
129 }
130
131 /**
132 * Check if we have an installation failed state.
133 * @return bool
134 */
135 public function installation_failed(){
136 $installation_active = get_option("rsssl_le_start_installation");
137 $installation_failed = get_option("rsssl_installation_error");
138
139 return $installation_active && $installation_failed;
140 }
141
142 /**
143 * Cleanup. If user did not consent to storage, all password fields should be removed on activation, unless they're needed for renewals
144 *
145 * @return bool
146 */
147 public function cleanup_on_ssl_activation(){
148 if ( !current_user_can('manage_security') ) {
149 return false;
150 }
151 $delete_credentials = !rsssl_get_option('store_credentials');
152 $le_fields = rsssl_le_add_fields([]);
153 if ( !$this->certificate_automatic_install_possible() || !$this->certificate_install_required() || $delete_credentials ) {
154 $le_fields = array_filter($le_fields, function($i){
155 return isset( $i['type'] ) && $i['type'] === 'password';
156 });
157 $options = get_option( 'rsssl_options' );
158 foreach ($le_fields as $index => $field ) {
159 unset($options[$field['id']]);
160 }
161 update_option( 'rsssl_options', $options, false );
162 }
163 return true;
164 }
165
166 /**
167 * some custom actions after a field has been saved
168 * @param string $fieldname
169 * @param mixed $fieldvalue
170 * @param mixed $prev_value
171 * @param string $type
172 *
173 * @return void
174 */
175 public function after_save_field(
176 $fieldname, $fieldvalue, $prev_value, $type
177 ) {
178 rsssl_progress_add( 'domain' );
179 //only run when changes have been made
180 if ( $fieldvalue === $prev_value ) {
181 return;
182 }
183
184 $options = [
185 'directadmin_password',
186 'cpanel_password',
187 'cloudways_api_key',
188 'plesk_password',
189 ];
190
191 if ( in_array($fieldname, $options) && strpos( $fieldvalue, 'rsssl_' ) === false ) {
192 rsssl_update_option($fieldname, $this->encrypt_with_prefix($fieldvalue) );
193 }
194
195 if ( $fieldname==='other_host_type' ){
196 if ( !rsssl_do_local_lets_encrypt_generation() ) {
197 rsssl_progress_add('directories');
198 rsssl_progress_add('generation');
199 rsssl_progress_add('dns-verification');
200 }
201 }
202
203 if ( $fieldname==='email' ){
204 if ( !is_email($fieldvalue) ) {
205 rsssl_progress_remove('domain');
206 }
207 }
208 }
209
210 /**
211 * Test for localhost or subfolder usage
212 * @return RSSSL_RESPONSE
213 */
214 public function check_domain(){
215 $details = parse_url(site_url());
216 $path = isset($details['path']) ? $details['path'] : '';
217 if ( strpos(site_url(), 'localhost')!==false ) {
218 rsssl_progress_remove( 'system-status' );
219 $action = 'stop';
220 $status = 'error';
221 $message = __( "It is not possible to install Let's Encrypt on a localhost environment.", "really-simple-ssl" );
222 } else if (is_multisite() && get_current_blog_id() !== get_main_site_id() ) {
223 rsssl_progress_remove('system-status');
224 $action = 'stop';
225 $status = 'error';
226 $message = __("It is not possible to install Let's Encrypt on a subsite. Please go to the main site of your website.", "really-simple-ssl" );
227 } else if ( strlen($path)>0 ) {
228 rsssl_progress_remove('system-status');
229 $action = 'stop';
230 $status = 'error';
231 $message = __("It is not possible to install Let's Encrypt on a subfolder configuration.", "really-simple-ssl" ).rsssl_le_read_more(rsssl_link('install-ssl-on-subfolders') );
232 } elseif ( rsssl_caa_record_prevents_le() ) {
233 $action = 'stop';
234 $status = 'error';
235 $message = __("Please adjust the CAA records via your DNS provider to allow Let’s Encrypt SSL certificates", "really-simple-ssl" ).rsssl_le_read_more(rsssl_link('instructions/edit-dns-caa-records-to-allow-lets-encrypt-ssl-certificates/') );
236 } else {
237 $action = 'continue';
238 $status = 'success';
239 $message = __("Your domain meets the requirements for Let's Encrypt.", "really-simple-ssl" );
240 }
241 return new RSSSL_RESPONSE($status, $action, $message);
242 }
243
244 /**
245 * Get certificate installation URL
246 * @return RSSSL_RESPONSE
247 */
248
249 public function search_ssl_installation_url(){
250 //start with most generic, then more specific if possible.
251 $url = rsssl_link('install-ssl-certificate');
252 $host = 'enter-your-dashboard-url-here';
253
254 if (function_exists('wp_get_direct_update_https_url') && !empty(wp_get_direct_update_https_url())) {
255 $url = wp_get_direct_update_https_url();
256 }
257
258 if ( rsssl_is_cpanel() ) {
259 $cpanel = new rsssl_cPanel();
260 $host = $cpanel->host;
261 $url = $cpanel->ssl_installation_url;
262 } else if ( rsssl_is_plesk() ) {
263 $plesk = new rsssl_plesk();
264 $host = $plesk->host;
265 $url = $plesk->ssl_installation_url;
266 } else if ( rsssl_is_directadmin() ) {
267 $directadmin = new rsssl_directadmin();
268 $host = $directadmin->host;
269 $url = $directadmin->ssl_installation_url;
270 }
271
272 $hosting_company = rsssl_get_other_host();
273 if ( $hosting_company && $hosting_company !== 'none' ) {
274 $hosting_specific_link = RSSSL_LE()->hosts->getKnownHosts()[$hosting_company]['ssl_installation_link'];
275 if ($hosting_specific_link) {
276 $site = trailingslashit( str_replace(array('https://','http://', 'www.'),'', site_url()) );
277 if ( strpos($hosting_specific_link,'{host}') !==false && empty($host) ) {
278 $url = '';
279 } else {
280 $url = str_replace(array('{host}', '{domain}'), array($host, $site), $hosting_specific_link);
281 }
282 }
283 }
284
285 $action = 'continue';
286 $status = 'warning';
287 $message = rsssl_get_manual_instructions_text($url);
288 $output = $url;
289 return new RSSSL_RESPONSE($status, $action, $message, $output );
290 }
291
292 /**
293 * Test for localhost usage
294 * @return RSSSL_RESPONSE
295 */
296 public function certificate_status(){
297 delete_transient('rsssl_certinfo');
298 if ( RSSSL()->certificate->is_valid() ) {
299 //we have now renewed the cert info transient
300 $certinfo = get_transient('rsssl_certinfo');
301 $end_date = isset($certinfo['validTo_time_t']) ? $certinfo['validTo_time_t'] : false;
302 $grace_period = strtotime('+'.rsssl_le_manual_generation_renewal_check.' days');
303 $expiry_date = !empty($end_date) ? date( get_option('date_format'), $end_date ) : __("(unknown)","really-simple-ssl");
304 //if the certificate expires within the grace period, allow renewal
305 //e.g. expiry date 30 may, now = 10 may => grace period 9 june.
306 if ( $grace_period > $end_date ) {
307 $action = 'continue';
308 $status = 'success';
309 $message = sprintf(__("Your certificate will expire on %s.", "really-simple-ssl" ).' '.__("Continue to renew.", "really-simple-ssl" ), $expiry_date); ;
310 } else {
311 $action = 'continue';
312 $status = 'error';
313 $message = __("You already have a valid SSL certificate.", "really-simple-ssl" );
314 }
315
316 } else {
317 $action = 'continue';
318 $status = 'success';
319 $message = __("SSL certificate should be generated and installed.", "really-simple-ssl" );
320 }
321 return new RSSSL_RESPONSE($status, $action, $message);
322 }
323
324 /**
325 * Check if the certifiate is to expire in max rsssl_le_manual_generation_renewal_check days.
326 * Used in notices list
327 * @return bool
328 */
329
330 public function certificate_about_to_expire() {
331 $about_to_expire = RSSSL()->certificate->about_to_expire();
332 if ( !$about_to_expire ) {
333 //if the certificate is valid, stop any attempt to renew.
334 delete_option('rsssl_le_start_renewal');
335 delete_option('rsssl_le_start_installation');
336 return false;
337 } else {
338 return true;
339 }
340 }
341
342 /**
343 * Test for server software
344 * @return RSSSL_RESPONSE
345 */
346
347 public function server_software(){
348 $action = 'continue';
349 $status = 'warning';
350 $message = __("The Hosting Panel software was not recognized. Depending on your hosting provider, the generated certificate may need to be installed manually.", "really-simple-ssl" );
351
352 if ( rsssl_is_cpanel() ) {
353 $status = 'success';
354 $message = __("CPanel recognized. Possibly the certificate can be installed automatically.", "really-simple-ssl" );
355 } else if ( rsssl_is_plesk() ) {
356 $status = 'success';
357 $message = __("Plesk recognized. Possibly the certificate can be installed automatically.", "really-simple-ssl" );
358 } else if ( rsssl_is_directadmin() ) {
359 $status = 'success';
360 $message = __("DirectAdmin recognized. Possibly the certificate can be installed automatically.", "really-simple-ssl" );
361 }
362
363 return new RSSSL_RESPONSE($status, $action, $message);
364 }
365
366 /**
367 * Check if CURL is available
368 *
369 * @return RSSSL_RESPONSE
370 */
371
372 public function curl_exists() {
373 if( function_exists('curl_init') === false ){
374 $action = 'stop';
375 $status = 'error';
376 $message = __("The PHP function CURL is not available on your server, which is required. Please contact your hosting provider.", "really-simple-ssl" );
377 } else {
378 $action = 'continue';
379 $status = 'success';
380 $message = __("The PHP function CURL has successfully been detected.", "really-simple-ssl" );
381 }
382
383 return new RSSSL_RESPONSE($status, $action, $message);
384 }
385
386 /**
387 * Get or create an account
388 * @return RSSSL_RESPONSE
389 */
390 public function get_account() {
391 $account_email = $this->account_email();
392 if ( is_email($account_email) ) {
393 try {
394 $this->account
395 = ! Account::exists( $account_email ) ?
396 Account::create( $account_email ) :
397 Account::get( $account_email );
398 $status = 'success';
399 $action = 'continue';
400 $message = __("Successfully retrieved account", "really-simple-ssl");
401 } catch(Exception $e) {
402 $response = $this->get_error($e);
403 $status = 'error';
404 $action = 'retry';
405 if ( strpos($response, 'invalid contact domain')) {
406 $action = 'stop';
407 $response = __("The used domain for your email address is not allowed.","really-simple-ssl").'&nbsp;'.
408 sprintf(__("Please change your email address %shere%s and try again.", "really-simple-ssl"),'<a href="'.rsssl_letsencrypt_wizard_url().'&step=2'.'">','</a>');
409 }
410
411 $message = $response;
412 }
413 } else {
414 $status = 'error';
415 $action = 'stop';
416 $message = __("The email address was not set. Please set the email address",'really-simple-ssl');
417 }
418 return new RSSSL_RESPONSE($status, $action, $message);
419 }
420
421 /**
422 * @return RSSSL_RESPONSE
423 */
424 public function get_dns_token()
425 {
426 if ( rsssl_is_ready_for('dns-verification') ) {
427 $use_dns = rsssl_dns_verification_required();
428 $challenge_type = $use_dns ? Order::CHALLENGE_TYPE_DNS : Order::CHALLENGE_TYPE_HTTP;
429 if ( $use_dns ) {
430 try {
431 $this->get_account();
432 $dnsWriter = new class extends AbstractDNSWriter {
433 public function write( Order $order, string $identifier, string $digest ): bool {
434 $tokens = get_option( 'rsssl_le_dns_tokens', [] );
435 $tokens[ $identifier ] = $digest;
436 update_option( "rsssl_le_dns_tokens", $tokens, false );
437 rsssl_progress_add( 'dns-verification' );
438
439 //return false, as we will continue later on.
440 return false;
441 }
442 };
443 DNS::setWriter( $dnsWriter );
444 $response = $this->get_order();
445 $order = $response->output;
446 $response->output = false;
447
448 if ( $order ) {
449 try {
450 if ( $order->authorize( $challenge_type ) ) {
451 $response = new RSSSL_RESPONSE(
452 'success',
453 'continue',
454 __( "Token successfully retrieved. Click the refresh button if it's not visible yet.", 'really-simple-ssl' ),
455 $this->get_dns_tokens()
456 );
457 } else {
458 if ( get_option( 'rsssl_le_dns_tokens' ) ) {
459 $response = new RSSSL_RESPONSE(
460 'success',
461 'continue',
462 __( "Token successfully retrieved. Click the refresh button if it's not visible yet.", 'really-simple-ssl' ),
463 $this->get_dns_tokens()
464 );
465 } else {
466 $response = new RSSSL_RESPONSE(
467 'error',
468 'retry',
469 __( "Token not received yet.", 'really-simple-ssl' )
470 );
471 }
472
473 }
474 } catch ( Exception $e ) {
475 $error = $this->get_error( $e );
476 if ( strpos( $error, 'No challenge found with given type')!==false ) {
477 //Maybe it was first set to HTTP challenge. retry after clearing the order.
478 $this->clear_order(true);
479 } else if (strpos($error, 'Keys exist already')!==false) {
480 $this->clear_order(true);
481 $error = __("DNS token not retrieved.", 'really-simple-ssl').' '.__("There are existing keys, the order had to be cleared first.","really-simple-ssl")." ".__("Please start at the previous step.","really-simple-ssl");
482 } else if (strpos($error, 'Order has status "invalid"')!==false) {
483 $this->clear_order();
484 $error = __("DNS token not retrieved.", 'really-simple-ssl').' '.__("The order is invalid, possibly due to too many failed authorization attempts. Please start at the previous step.","really-simple-ssl");
485 } else
486 //fixing a plesk bug
487 if ( strpos($error, 'No order for ID ') !== FALSE){
488 $error .= '&nbsp;'.__("Order ID mismatch, regenerate order.","really-simple-ssl");
489 $this->clear_order();
490 rsssl_progress_remove('dns-verification');
491 $error .= '&nbsp;'.__("If you entered your DNS records before, they need to be changed.","really-simple-ssl");
492 }
493 $response = new RSSSL_RESPONSE(
494 'error',
495 'retry',
496 $error
497 );
498 }
499 } else {
500 if (strpos($response->message, 'Keys exist already')!==false) {
501 $this->clear_order(true);
502 $response->message = __("DNS token not retrieved.", 'really-simple-ssl').' '.__("There are existing keys, the order had to be cleared first.","really-simple-ssl")." ".__("Please start at the previous step.","really-simple-ssl");
503 }
504 return $response;
505 }
506 } catch ( Exception $e ) {
507 rsssl_progress_remove( 'dns-verification' );
508 $response = $this->get_error( $e );
509 $response = new RSSSL_RESPONSE(
510 'error',
511 'retry',
512 $response
513 );
514 }
515 } else {
516 $response = new RSSSL_RESPONSE(
517 'error',
518 'stop',
519 __( "Configured for HTTP challenge", 'really-simple-ssl' )
520 );
521 }
522 } else {
523 rsssl_progress_remove( 'dns-verification' );
524 $response = new RSSSL_RESPONSE(
525 'error',
526 'stop',
527 $this->not_completed_steps_message('dns-verification')
528 );
529 }
530 return $response;
531 }
532
533 /**
534 * Get the DNS tokens
535 */
536 public function get_dns_tokens(): array
537 {
538 $tokens = get_option( 'rsssl_le_dns_tokens', [] );
539 $output = [];
540 foreach ($tokens as $domain => $token ) {
541 $output[] = [
542 'domain' => $domain,
543 'token' => $token,
544 ];
545 }
546 return $output;
547 }
548
549 /**
550 * Check DNS txt records.
551 */
552 public function verify_dns(): RSSSL_RESPONSE
553 {
554 if ( rsssl_is_ready_for('generation') ) {
555 update_option('rsssl_le_dns_records_verified', false, false );
556
557 $tokens = get_option('rsssl_le_dns_tokens');
558 if ( !$tokens) {
559 $status = 'error';
560 $action = 'stop';
561 $message = __('Token not generated. Please complete the previous step.',"really-simple-ssl");
562 return new RSSSL_RESPONSE($status, $action, $message);
563 }
564 foreach ($tokens as $identifier => $token){
565 if (strpos($identifier, '*') !== false) continue;
566 set_error_handler(array($this, 'custom_error_handling'));
567 ini_set('dns_cache_expiry', 0);
568 $response = dns_get_record( "_acme-challenge.$identifier", DNS_TXT );
569 restore_error_handler();
570 if ( isset($response[0]['txt']) ){
571 if ($response[0]['txt'] === $token) {
572 $response = new RSSSL_RESPONSE(
573 'success',
574 'continue',
575 sprintf(__('Successfully verified DNS records', "really-simple-ssl"), "_acme-challenge.$identifier")
576 );
577 update_option('rsssl_le_dns_records_verified', true, false );
578 } else {
579 $ttl = $response[0]['ttl'] ?? 0;
580 $ttl = $this->format_duration($ttl);
581 $action = get_option('rsssl_skip_dns_check') ? 'continue' : 'stop';
582 $response = new RSSSL_RESPONSE(
583 'error',
584 $action,
585 sprintf(__('The DNS response for %s was %s, while it should be %s.', "really-simple-ssl"), "_acme-challenge.$identifier", $response[0]['txt'], $token ). ' '.
586 sprintf(__("Please wait %s before trying again, as this is the expiration of the DNS record currently.", 'really-simple-ssl'), $ttl)
587 );
588 break;
589 }
590 } else {
591 $action = get_option('rsssl_skip_dns_check') ? 'continue' : 'stop';
592 $response = new RSSSL_RESPONSE(
593 'warning',
594 $action,
595 sprintf(__('Could not verify TXT record for domain %s', "really-simple-ssl"), "_acme-challenge.$identifier")
596 );
597 }
598 }
599
600 } else {
601 $response = new RSSSL_RESPONSE(
602 'error',
603 'stop',
604 $this->not_completed_steps_message('dns-verification')
605 );
606 }
607
608 return $response;
609 }
610
611 private function format_duration($seconds)
612 {
613 $seconds = (int) $seconds;
614 if ($seconds >= 3600) {
615 $hours = floor($seconds / 3600);
616 $minutes = floor(($seconds % 3600) / 60);
617 $secs = $seconds % 3600 % 60;
618 return sprintf(__("%d:%02d:%02d hours", 'really-simple-ssl'), $hours, $minutes, $secs);
619 } elseif ($seconds >= 60) {
620 $minutes = floor($seconds / 60);
621 $secs = $seconds % 60;
622 return sprintf(__("%d:%02d minutes", 'really-simple-ssl'), $minutes, $secs);
623 } else {
624 return sprintf(__("%d seconds", 'really-simple-ssl'), $seconds);
625 }
626 }
627
628 /**
629 * Clear an existing order
630 */
631 public function clear_order( $clear_keys = false )
632 {
633 if ( $clear_keys ) {
634 $this->clear_keys_directory();
635 }
636 $this->get_account();
637 if ( $this->account ) {
638 $response = $this->get_order();
639 $order = $response->output;
640 if ( $order ) {
641 $order->clear();
642 }
643 }
644 }
645
646 /**
647 * Authorize the order
648 */
649 public function create_bundle_or_renew(): RSSSL_RESPONSE
650 {
651 $bundle_completed = false;
652 $use_dns = rsssl_dns_verification_required();
653 $attempt_count = (int) get_transient( 'rsssl_le_generate_attempt_count' );
654 if ( $attempt_count>10 ){
655 delete_option("rsssl_le_start_renewal");
656 $message = __("The certificate generation was rate limited for 5 minutes because the authorization failed.",'really-simple-ssl');
657 if ($use_dns){
658 $message .= '&nbsp;'.__("Please double check your DNS txt record.",'really-simple-ssl');
659 }
660 return new RSSSL_RESPONSE(
661 'error',
662 'stop',
663 $message
664 );
665 }
666
667 if ( !get_option('rsssl_skip_dns_check') ) {
668 if ( $use_dns && ! get_option( 'rsssl_le_dns_records_verified' ) ) {
669 return new RSSSL_RESPONSE(
670 'error',
671 'stop',
672 __( "DNS records were not verified yet. Please complete the previous step.", 'really-simple-ssl' )
673 );
674 }
675 }
676
677 if ( rsssl_is_ready_for('generation') ) {
678
679 $this->get_account();
680 if ( $use_dns ) {
681 if ( defined('WP_DEBUG') && WP_DEBUG ) {
682 error_log( "DNS verified: " . get_option( 'rsssl_le_dns_records_verified' ) );
683 error_log( "Skip DNS check: " . get_option( 'rsssl_skip_dns_check' ) );
684 }
685 $dnsWriter = new class extends AbstractDNSWriter {
686 public function write( Order $order, string $identifier, string $digest): bool {
687 $status = false;
688 if ( get_option('rsssl_le_dns_tokens') ) {
689 $status = true;
690 }
691 return $status;
692 }
693 };
694 DNS::setWriter($dnsWriter);
695 }
696
697 $response = $this->get_order();
698 $order = $response->output;
699 $response->output = false;
700
701 if ( $order ) {
702 if ( $order->isCertificateBundleAvailable() ) {
703
704 try {
705 $order->enableAutoRenewal();
706 $bundle_completed = $this->update_certificate_paths($order);
707 if ( $bundle_completed ) {
708 $response = new RSSSL_RESPONSE(
709 'success',
710 'continue',
711 __("Certificate already generated. It was renewed if required.",'really-simple-ssl')
712 );
713 } else {
714 $response = new RSSSL_RESPONSE(
715 'error',
716 'retry',
717 __("Files not created yet...",'really-simple-ssl')
718 );
719 }
720 } catch ( Exception $e ) {
721 $response = new RSSSL_RESPONSE(
722 'error',
723 'retry',
724 $this->get_error( $e )
725 );
726 $bundle_completed = false;
727 }
728 } else {
729 $finalized = false;
730 $challenge_type = $use_dns ? Order::CHALLENGE_TYPE_DNS : Order::CHALLENGE_TYPE_HTTP;
731 try {
732 if ( $order->authorize( $challenge_type ) ) {
733 $order->finalize();
734 $this->reset_attempt();
735 $finalized = true;
736 } else {
737 $this->count_attempt();
738 $response = new RSSSL_RESPONSE(
739 'error',
740 'retry',
741 __('Authorization not completed yet.',"really-simple-ssl")
742 );
743 $bundle_completed = false;
744 }
745 } catch ( Exception $e ) {
746 $this->count_attempt();
747 $message = $this->get_error( $e );
748 if ( defined('WP_DEBUG') && WP_DEBUG ) {
749 error_log("Really Simple Security: ".$message);
750 }
751 $response = new RSSSL_RESPONSE(
752 'error',
753 'stop',
754 $message
755 );
756 if ( strpos( $message, 'No challenge found with given type')!==false ) {
757 //Maybe it was first set to HTTP challenge. retry after clearing the order.
758 $response->message = __("Due to a change in challenge type, the order had to be reset. Please start at the previous step.","really-simple-ssl");
759 $this->clear_order(true);
760 } else if ( strpos($message, 'Order has status "invalid"')!==false) {
761 $this->clear_order();
762 $response->message = __("Certificate not created.", 'really-simple-ssl').' '.__("The order is invalid, possibly due to too many failed authorization attempts. Please start at the previous step.","really-simple-ssl");
763 if ($use_dns) {
764 rsssl_progress_remove('dns-verification');
765 $response->message .= '&nbsp;'.__("As your order will be regenerated, you'll need to update your DNS text records.","really-simple-ssl");
766 }
767 } else {
768 //if OCSP is not disabled yet, and the order status is not invalid, we disable ocsp, and try again.
769 if ( !rsssl_get_option( 'disable_ocsp' ) ) {
770 rsssl_update_option( 'disable_ocsp', true );
771 $response->action = 'retry';
772 $response->status = 'warning';
773 $response->message = __("OCSP not supported, the certificate will be generated without OCSP.","really-simple-ssl");
774 }
775 }
776 }
777
778 if ( $finalized ) {
779 try {
780 if ( $order->isCertificateBundleAvailable() ) {
781 $bundle_completed = $this->update_certificate_paths($order);
782
783 if ( $bundle_completed ) {
784 $response = new RSSSL_RESPONSE(
785 'success',
786 'continue',
787 __("Successfully generated certificate.",'really-simple-ssl')
788 );
789 } else {
790 $response = new RSSSL_RESPONSE(
791 'error',
792 'retry',
793 __("Files not created yet...",'really-simple-ssl')
794 );
795 }
796
797 } else {
798 $response = new RSSSL_RESPONSE(
799 'error',
800 'retry',
801 __("Bundle not available yet...",'really-simple-ssl')
802 );
803 }
804 } catch ( Exception $e ) {
805 $response = new RSSSL_RESPONSE(
806 'error',
807 'retry',
808 $this->get_error( $e )
809 );
810 }
811 }
812 }
813 }
814 } else {
815 $response = new RSSSL_RESPONSE(
816 'error',
817 'stop',
818 $this->not_completed_steps_message('generation')
819 );
820 }
821
822 if ( $bundle_completed ){
823 rsssl_progress_add('generation');
824 update_option('rsssl_le_certificate_generated_by_rsssl', true, false);
825 delete_option("rsssl_le_start_renewal");
826 } else {
827 rsssl_progress_remove('generation');
828 }
829
830 return $response;
831 }
832
833 /**
834 * For each file, check if the path exists, update the path options, and
835 * return success or false accordingly
836 */
837 private function update_certificate_paths($order): bool
838 {
839 $bundle = $order->getCertificateBundle();
840 $pathToPrivateKey = $bundle->path . $bundle->private;
841 $pathToCertificate = $bundle->path . $bundle->certificate;
842 $pathToIntermediate = $bundle->path . $bundle->intermediate;
843 $success_private = $success_cert = $success_intermediate = false;
844
845 if ( file_exists( $pathToPrivateKey ) ) {
846 $success_private = true;
847 update_option( 'rsssl_private_key_path', $pathToPrivateKey, false );
848 }
849 if ( file_exists( $pathToCertificate ) ) {
850 $success_cert = true;
851 update_option( 'rsssl_certificate_path', $pathToCertificate, false );
852 }
853
854 if ( file_exists( $pathToIntermediate ) ) {
855 $success_intermediate = true;
856 update_option( 'rsssl_intermediate_path', $pathToIntermediate, false );
857 }
858
859 $bundle_completed = true;
860 if ( ! $success_cert || ! $success_private || ! $success_intermediate ) {
861 $bundle_completed = false;
862 }
863
864 return $bundle_completed;
865 }
866
867 /**
868 * Get the order object
869 */
870 public function get_order(): RSSSL_RESPONSE
871 {
872 // if we don't have an account, try to retrieve it
873 if ( !$this->account ) {
874 $this->get_account();
875 }
876
877 // still no account, then exit
878 if ( !$this->account ) {
879 return new RSSSL_RESPONSE(
880 'error',
881 'retry',
882 __( "Failed retrieving account.", 'really-simple-ssl' )
883 );
884 }
885
886 if ( ! Order::exists( $this->account, $this->subjects ) ) {
887 try {
888 $response = new RSSSL_RESPONSE(
889 'success',
890 'continue',
891 __("Order successfully created.",'really-simple-ssl')
892 );
893 $response->output = Order::create( $this->account, $this->subjects );
894
895 } catch(Exception $e) {
896 $response = new RSSSL_RESPONSE(
897 'error',
898 'retry',
899 $this->get_error($e)
900 );
901 }
902 } else {
903 //order exists already
904 $response = new RSSSL_RESPONSE(
905 'success',
906 'continue',
907 __( "Order successfully retrieved.", 'really-simple-ssl' )
908 );
909 $response->output = Order::get( $this->account, $this->subjects );
910 }
911
912 return $response;
913 }
914
915 /**
916 * Keep track of certain request counts, to prevent rate limiting by LE
917 */
918 public function count_attempt()
919 {
920 $attempt_count = (int) get_transient( 'rsssl_le_generate_attempt_count' );
921 $attempt_count++;
922 set_transient('rsssl_le_generate_attempt_count', $attempt_count, 5 * MINUTE_IN_SECONDS);
923 }
924
925 public function reset_attempt(){
926 delete_transient('rsssl_le_generate_attempt_count');
927 }
928
929 /**
930 * Check if SSL generation renewal can be handled automatically
931 */
932 public function ssl_generation_can_auto_renew(): bool
933 {
934 if ( rsssl_get_option('verification_type')==='dns' && !get_option('rsssl_le_dns_configured_by_rsssl') ) {
935 return false;
936 }
937
938 return true;
939 }
940
941 /**
942 * Check if it's possible to autorenew
943 */
944 public function certificate_automatic_install_possible(): bool
945 {
946
947 $install_method = get_option('rsssl_le_certificate_installed_by_rsssl');
948
949 //if it was never auto installed, we probably can't autorenew.
950 if ($install_method === false ) {
951 return false;
952 }
953
954 return true;
955 }
956
957 /**
958 * Check if the manual renewal should start.
959 */
960 public function should_start_manual_installation_renewal(): bool
961 {
962 if ( !$this->should_start_manual_ssl_generation() && get_option( "rsssl_le_start_installation" ) ) {
963 return true;
964 }
965 return false;
966 }
967
968 public function should_start_manual_ssl_generation()
969 {
970 return get_option( "rsssl_le_start_renewal" );
971 }
972
973 /**
974 * Only used if
975 * - SSL generated by RSSSL
976 * - certificate is about to expire
977 */
978 public function certificate_renewal_status_notice(): string
979 {
980 if ( !RSSSL_LE()->letsencrypt_handler->ssl_generation_can_auto_renew()){
981 return 'manual-generation';
982 }
983
984 if ( $this->certificate_install_required() &&
985 $this->certificate_automatic_install_possible() &&
986 $this->installation_failed()
987 ){
988 return 'automatic-installation-failed';
989 }
990
991 if ( $this->certificate_install_required() && !$this->certificate_automatic_install_possible() ) {
992 return 'manual-installation';
993 }
994
995 return 'automatic';
996 }
997
998 /**
999 * Check if the certificate has to be installed on each renewal defaults to
1000 * true.
1001 */
1002 public function certificate_install_required(): bool
1003 {
1004 $install_method = get_option('rsssl_le_certificate_installed_by_rsssl');
1005 $hosting_company = rsssl_get_other_host();
1006 $hosts = RSSSL_LE()->hosts;
1007 if ( in_array($install_method, $hosts->no_installation_renewal_needed) || in_array($hosting_company, $hosts->no_installation_renewal_needed)) {
1008 return false;
1009 }
1010
1011 return true;
1012 }
1013
1014 /**
1015 * Check if the certificate needs renewal.
1016 */
1017 public function cron_certificate_needs_renewal(): bool
1018 {
1019 $cert_file = get_option('rsssl_certificate_path');
1020 if ( empty($cert_file) ) {
1021 return false;
1022 }
1023
1024 $certificate = file_get_contents($cert_file);
1025 $certificateInfo = openssl_x509_parse($certificate);
1026 $valid_to = $certificateInfo['validTo_time_t'];
1027 $in_expiry_days = strtotime( "+".rsssl_le_cron_generation_renewal_check." days" );
1028 if ( $in_expiry_days > $valid_to ) {
1029 return true;
1030 }
1031 return false;
1032 }
1033
1034
1035 /**
1036 * Get account email
1037 * @return string
1038 */
1039 public function account_email()
1040 {
1041 //don't use the default value: we want users to explicitly enter a value
1042 return rsssl_get_option('email_address');
1043 }
1044
1045 /**
1046 * Get terms accepted
1047 */
1048 public function terms_accepted(): RSSSL_RESPONSE
1049 {
1050 //don't use the default value: we want users to explicitly enter a value
1051 $accepted = rsssl_get_option('accept_le_terms');
1052 if ( $accepted ) {
1053 $status = 'success';
1054 $action = 'continue';
1055 $message = __("Terms & Conditions are accepted.",'really-simple-ssl');
1056 } else {
1057 $status = 'error';
1058 $action = 'stop';
1059 $message = __("The Terms & Conditions were not accepted. Please accept in the general settings.",'really-simple-ssl');
1060 }
1061
1062 return new RSSSL_RESPONSE($status, $action, $message);
1063 }
1064
1065
1066
1067 /**
1068 * Change the email address in an account
1069 * @param $new_email
1070 */
1071 public function update_account( $new_email )
1072 {
1073 if (!$this->account) return;
1074
1075 try {
1076 $this->account->update($new_email);
1077 } catch (Exception $e) {
1078 }
1079 }
1080
1081 /**
1082 * Get list of common names on the certificate
1083 */
1084 public function get_subjects(): array
1085 {
1086 $subjects = array();
1087 $domain = rsssl_get_domain();
1088 $root = str_replace( 'www.', '', $domain );;
1089 $subjects[] = $domain;
1090 //don't offer aliasses for subdomains
1091 if ( !rsssl_is_subdomain() ) {
1092 if (rsssl_get_option( 'include_alias' )) {
1093 //main is www.
1094 if ( strpos( $domain, 'www.' ) !== false ) {
1095 $alias_domain = $root;
1096 } else {
1097 $alias_domain = 'www.'.$root;
1098 }
1099 $subjects[] = $alias_domain;
1100 }
1101 }
1102
1103 if ( rsssl_wildcard_certificate_required() ) {
1104 $domain = rsssl_get_domain();
1105 //in theory, the main site of a subdomain setup can be a www. domain. But we have to request a certificate without the www.
1106 $domain = str_replace( 'www.', '', $domain );
1107 $subjects = array(
1108 $domain,
1109 '*.' . $domain,
1110 );
1111 }
1112
1113 return apply_filters('rsssl_le_subjects', $subjects);
1114 }
1115
1116 /**
1117 * Check if we're ready for the next step.
1118 * @param string $item
1119 *
1120 * @return array | bool
1121 */
1122 public function is_ready_for($item)
1123 {
1124 if ( !rsssl_do_local_lets_encrypt_generation() ) {
1125 rsssl_progress_add('directories');
1126 rsssl_progress_add('generation');
1127 rsssl_progress_add('dns-verification');
1128 }
1129
1130 if ( !rsssl_dns_verification_required() ) {
1131 rsssl_progress_add('dns-verification');
1132 }
1133
1134 if (empty(rsssl_get_not_completed_steps($item))){
1135 return true;
1136 }
1137
1138 return false;
1139 }
1140
1141 /**
1142 * Catch errors
1143 *
1144 * @since 3.0
1145 *
1146 * @access public
1147 * @param $errno
1148 * @param $errstr
1149 * @param $errfile
1150 * @param $errline
1151 * @param array $errcontext
1152 *
1153 * @return bool
1154 */
1155 public function custom_error_handling( $errno, $errstr, $errfile, $errline, $errcontext = array() ) {
1156 return true;
1157 }
1158
1159 public function not_completed_steps_message($step)
1160 {
1161 $not_completed_steps = rsssl_get_not_completed_steps($step);
1162 $nice_names = array();
1163 $steps = rsssl_le_steps();
1164 foreach ($not_completed_steps as $not_completed_step ) {
1165 $index = array_search($not_completed_step, array_column( $steps, 'id'));
1166 $nice_names[] = $steps[$index]['title'];
1167 }
1168 return sprintf(__('Please complete the following step(s) first: %s', "really-simple-ssl"), implode(", ", $nice_names) );
1169 }
1170
1171 /**
1172 * Test for writing permissions
1173 * @return RSSSL_RESPONSE
1174 */
1175 public function check_writing_permissions(): RSSSL_RESPONSE
1176 {
1177 $directories_without_permissions = $this->directories_without_writing_permissions();
1178 $has_missing_permissions = count($directories_without_permissions)>0;
1179
1180 if ( $has_missing_permissions ) {
1181 rsssl_progress_remove('directories');
1182 $action = 'stop';
1183 $status = 'error';
1184 $message = __("The following directories do not have the necessary writing permissions.", "really-simple-ssl" )."&nbsp;".__("Set permissions to 644 to enable SSL generation.", "really-simple-ssl" );
1185 foreach ($directories_without_permissions as $directories_without_permission) {
1186 $message .= "<br> - ".$directories_without_permission;
1187 }
1188 } else {
1189 $action = 'continue';
1190 $status = 'success';
1191 $message = __("The required directories have the necessary writing permissions.", "really-simple-ssl" );
1192 }
1193
1194 return new RSSSL_RESPONSE($status, $action, $message);
1195 }
1196
1197 /**
1198 * Verify if a host has been selected, and if so, if this host supports LE, or if it's already active
1199 */
1200 public function check_host(): RSSSL_RESPONSE
1201 {
1202 $action = 'continue';
1203 $status = 'success';
1204 $message = __("We have not detected any known hosting limitations.", "really-simple-ssl" );
1205 $host = rsssl_get_other_host();
1206 if ( $host === 'none' ) $host = false;
1207 $known_hosts = RSSSL_LE()->hosts->getKnownHosts();
1208 if ( isset($known_hosts[$host]) ){
1209 if ( $known_hosts[$host]['free_ssl_available'] === 'paid_only' ) {
1210 $action = 'stop';
1211 $status = 'error';
1212 $message = sprintf(__("According to our information, your hosting provider does not allow any kind of SSL installation, other than their own paid certificate. For an alternative hosting provider with SSL, see this %sarticle%s.","really-simple-ssl"), '<a target="_blank" href="https://really-simple-ssl.com/hosting-providers-with-free-ssl">', '</a>');
1213 }
1214
1215 if ( $known_hosts[$host]['free_ssl_available'] === 'activated_by_default' ) {
1216 $url = $known_hosts[$host]['ssl_installation_link'];
1217 $action = 'continue';
1218 $status = 'error';
1219 $message = sprintf(__("According to our information, your hosting provider supplies your account with an SSL certificate by default. Please contact your %shosting support%s if this is not the case.","really-simple-ssl"), '<a target="_blank" href="'.$url.'">', '</a>').'&nbsp'.
1220 __("After completing the installation, you can let Really Simple Security automatically configure your site for SSL by using the 'Activate SSL' button.","really-simple-ssl");
1221 }
1222 }
1223 return new RSSSL_RESPONSE($status, $action, $message);
1224 }
1225
1226 /**
1227 * Test for directory
1228 */
1229 public function check_challenge_directory(): RSSSL_RESPONSE
1230 {
1231 if ( !$this->challenge_directory() ) {
1232 rsssl_progress_remove('directories');
1233 $action = 'stop';
1234 $status = 'error';
1235 $message = __("The challenge directory is not created yet.", "really-simple-ssl" );
1236 } else {
1237 $action = 'continue';
1238 $status = 'success';
1239 $message = __("The challenge directory was successfully created.", "really-simple-ssl" );
1240 }
1241
1242 return new RSSSL_RESPONSE($status, $action, $message);
1243 }
1244
1245 /**
1246 * Test for directory
1247 */
1248 public function check_key_directory(): RSSSL_RESPONSE
1249 {
1250 $action = 'stop';
1251 $status = 'error';
1252 $message = __("The key directory is not created yet.", "really-simple-ssl" );
1253 //this option is set in the key_dir function, so we need to check it now.
1254 if ( !get_option('rsssl_create_folders_in_root')) {
1255 $action = 'retry';
1256 $message = __("Trying to create directory in root of website.", "really-simple-ssl" );
1257 }
1258
1259 if ( !$this->key_directory() ) {
1260 rsssl_progress_remove('directories');
1261 } else {
1262 $action = 'continue';
1263 $status = 'success';
1264 $message = __("The key directory was successfully created.", "really-simple-ssl" );
1265 }
1266 return new RSSSL_RESPONSE($status, $action, $message);
1267 }
1268
1269 /**
1270 * Test for directory
1271 */
1272 public function check_certs_directory(): RSSSL_RESPONSE
1273 {
1274 if ( !$this->certs_directory() ) {
1275 rsssl_progress_remove('directories');
1276 $action = 'stop';
1277 $status = 'error';
1278 $message = __("The certs directory is not created yet.", "really-simple-ssl" );
1279 } else {
1280 $action = 'continue';
1281 $status = 'success';
1282 $message = __("The certs directory was successfully created.", "really-simple-ssl" );
1283 }
1284 return new RSSSL_RESPONSE($status, $action, $message);
1285 }
1286
1287 /**
1288 * Check if our created directories have the necessary writing permissions
1289 */
1290
1291 public function directories_without_writing_permissions( ){
1292 $required_folders = array(
1293 $this->key_directory,
1294 $this->certs_directory,
1295 );
1296
1297 if ( !rsssl_dns_verification_required() ) {
1298 $required_folders[] = $this->challenge_directory;
1299 }
1300
1301 $no_writing_permissions = array();
1302 foreach ($required_folders as $required_folder){
1303 if (!$this->directory_has_writing_permissions( $required_folder )) {
1304 $no_writing_permissions[] = $required_folder;
1305 }
1306 }
1307
1308 return $no_writing_permissions;
1309 }
1310
1311 /**
1312 * Check if a directory has writing permissions
1313 * @param string $directory
1314 *
1315 * @return bool
1316 */
1317 public function directory_has_writing_permissions( $directory ){
1318 set_error_handler(array($this, 'custom_error_handling'));
1319 $test_file = fopen( $directory . "/really-simple-ssl-permissions-check.txt", "w" );
1320 if ( !$test_file ) {
1321 return false;
1322 }
1323
1324 fwrite($test_file, 'file to test writing permissions for Really Simple Security');
1325 fclose( $test_file );
1326 restore_error_handler();
1327 if (!file_exists($directory . "/really-simple-ssl-permissions-check.txt")) {
1328 return false;
1329 }
1330 return true;
1331 }
1332
1333 /**
1334 * Check if the challenge directory is reachable over the http protocol
1335 * @return RSSSL_RESPONSE
1336 */
1337 public function challenge_directory_reachable()
1338 {
1339 $file_content = false;
1340 $status_code = __('no response','really-simple-ssl');
1341 //make sure we request over http, otherwise the request might fail if the url is already https.
1342 $url = str_replace('https://', 'http://', site_url('.well-known/acme-challenge/really-simple-ssl-permissions-check.txt'));
1343
1344 $error_message = sprintf(__( "Could not reach challenge directory over %s.", "really-simple-ssl"), '<a target="_blank" href="'.$url.'">'.$url.'</a>');
1345 $test_string = 'Really Simple Security';
1346 $folders = $this->directories_without_writing_permissions();
1347 if ( !$this->challenge_directory() || count($folders) !==0 ) {
1348 $status = 'error';
1349 $action = 'stop';
1350 $message = __( "Challenge directory not writable.", "really-simple-ssl");
1351 return new RSSSL_RESPONSE($status, $action, $message);
1352 }
1353
1354 $response = wp_remote_get( $url );
1355 if ( is_array( $response ) ) {
1356 $status_code = wp_remote_retrieve_response_code( $response );
1357 $file_content = wp_remote_retrieve_body( $response );
1358 }
1359
1360 if ( $status_code !== 200 ) {
1361 if (get_option('rsssl_skip_challenge_directory_request')) {
1362 $status = 'warning';
1363 $action = 'continue';
1364 $message = $error_message.' '.sprintf( __( "Error code %s", "really-simple-ssl" ), $status_code );
1365 } else {
1366 $status = 'error';
1367 $action = 'stop';
1368 $message = $error_message.' '.sprintf( __( "Error code %s", "really-simple-ssl" ), $status_code );
1369 rsssl_progress_remove('directories');
1370 }
1371 } else {
1372 if ( ! is_wp_error( $response ) && ( strpos( $file_content, $test_string ) !== false ) ) {
1373 $status = 'success';
1374 $action = 'continue';
1375 $message = __( "Successfully verified alias domain.", "really-simple-ssl" );
1376 set_transient('rsssl_alias_domain_available', 'available', 30 * MINUTE_IN_SECONDS );
1377 } else {
1378 $status = 'error';
1379 $action = 'stop';
1380 $message = $error_message;
1381 rsssl_progress_remove('directories');
1382 }
1383 }
1384
1385 return new RSSSL_RESPONSE($status, $action, $message);
1386 }
1387
1388 /**
1389 * Check if exists, create .well-known/acme-challenge directory if not
1390 * existing
1391 * @return bool|string
1392 */
1393 public function challenge_directory()
1394 {
1395 $root_directory = trailingslashit(ABSPATH);
1396 if ( ! file_exists( $root_directory . '.well-known' ) ) {
1397 mkdir( $root_directory . '.well-known', 0755 );
1398 }
1399
1400 if ( ! file_exists( $root_directory . '.well-known/acme-challenge' ) ) {
1401 mkdir( $root_directory . '.well-known/acme-challenge', 0755 );
1402 }
1403
1404 if ( file_exists( $root_directory . '.well-known/acme-challenge' ) ){
1405 return $root_directory . '.well-known/acme-challenge';
1406 }
1407
1408 return false;
1409 }
1410
1411 /**
1412 * Get path to location where to create the directories.
1413 * @uses apply_filters 'rsssl_le_directory_path'
1414 */
1415 public function get_directory_path(): string
1416 {
1417 $root_directory = trailingslashit(ABSPATH);
1418 $directoryPath = trailingslashit(dirname($root_directory));
1419
1420 if ( get_option('rsssl_create_folders_in_root') ) {
1421 if ( !get_option('rsssl_ssl_dirname') ) {
1422 $token = str_shuffle ( time() );
1423 update_option('rsssl_ssl_dirname', $token, false );
1424 }
1425 if ( ! file_exists( $root_directory . get_option('rsssl_ssl_dirname') ) ) {
1426 mkdir( $root_directory . get_option('rsssl_ssl_dirname'), 0755 );
1427 }
1428 $directoryPath = ($root_directory . trailingslashit( get_option('rsssl_ssl_dirname') ));
1429 }
1430
1431 /**
1432 * Filter: 'rsssl_le_directory_path'
1433 * Can be used to change the directory path where the ssl/keys directory
1434 * is created.
1435 *
1436 * @param string $directoryPath
1437 * @return string
1438 */
1439 return apply_filters('rsssl_le_directory_path', $directoryPath);
1440 }
1441
1442 /**
1443 * Get path to ssl directory. Note that here is NO trailing slash.
1444 * @uses apply_filters 'rsssl_le_ssl_path'
1445 */
1446 public function get_ssl_path(): string
1447 {
1448 $directoryPath = $this->get_directory_path();
1449 $sslPath = ($directoryPath . 'ssl');
1450
1451 /**
1452 * Filter: 'rsssl_le_ssl_path'
1453 * Can be used to change the ssl path where the ssl/keys directory
1454 * is created.
1455 *
1456 * @param string $sslPath
1457 * @param string $directoryPath
1458 * @return string
1459 */
1460 return apply_filters('rsssl_le_ssl_path', $sslPath, $directoryPath);
1461 }
1462
1463 /**
1464 * Check if exists, create ssl/certs directory above the wp root if not existing
1465 * @return bool|string
1466 */
1467 public function certs_directory()
1468 {
1469 $sslPath = $this->get_ssl_path();
1470
1471 if (!file_exists($sslPath)) {
1472 mkdir($sslPath, 0755 );
1473 }
1474
1475 if (!file_exists( $sslPath . '/certs')) {
1476 mkdir( $sslPath . '/certs', 0755 );
1477 }
1478
1479 if (file_exists($sslPath . '/certs')) {
1480 return $sslPath . '/certs';
1481 }
1482
1483 return false;
1484 }
1485
1486 /**
1487 * Check if exists, create ssl/keys directory above the wp root if not
1488 * @return bool|string
1489 */
1490 public function key_directory()
1491 {
1492 $directory = $this->get_directory_path();
1493 $sslPath = $this->get_ssl_path();
1494
1495 try {
1496 $openbasedirHasRestrictions = $this->openbasedir_restriction($directory);
1497
1498 if ($openbasedirHasRestrictions === false) {
1499 if (!file_exists($sslPath) && is_writable($directory)) {
1500 mkdir($sslPath, 0755);
1501 }
1502
1503 if (!file_exists($sslPath . '/keys') && is_writable($sslPath)) {
1504 mkdir($sslPath . '/keys', 0755);
1505 }
1506
1507 if (file_exists($sslPath . '/keys')) {
1508 return $sslPath . '/keys';
1509 }
1510 }
1511
1512 // If creating the folder has failed, we're on apache, and can write
1513 // to these folders, we create a root directory.
1514 $has_writing_permissions = $this->directory_has_writing_permissions($this->challenge_directory);
1515
1516 // We're guessing that if the challenge dir has writing permissions,
1517 // the new dir will also have it.
1518 if (RSSSL()->server->uses_htaccess() && $has_writing_permissions) {
1519 update_option('rsssl_create_folders_in_root', true, false);
1520 }
1521 } catch ( Exception $e ) {
1522 return false;
1523 }
1524
1525 return false;
1526 }
1527
1528 /**
1529 * Check for openbasedir restrictions
1530 */
1531 private function openbasedir_restriction(string $path): bool
1532 {
1533
1534 // Default error handler is required
1535 set_error_handler(null);
1536
1537 // Clean last error info.
1538 error_clear_last();
1539
1540 // Testing...
1541 @file_exists($path);
1542
1543 // Restore previous error handler
1544 restore_error_handler();
1545
1546 // Return `true` if error has occurred
1547 return ($error = error_get_last()) && $error['message'] !== '__clean_error_info';
1548 }
1549
1550
1551 /**
1552 * Clear the keys directory, used in reset function
1553 * @since 5.0
1554 */
1555 public function clear_keys_directory()
1556 {
1557 if (!rsssl_user_can_manage()) {
1558 return;
1559 }
1560
1561 $dir = $this->key_directory();
1562 $this->delete_files_directories_recursively( $dir );
1563 }
1564
1565 /**
1566 * @param $dir
1567 * Delete files and directories recursively. Used to clear the order from keys directory
1568 * @since 5.0.11
1569 */
1570 private function delete_files_directories_recursively( $dir ) {
1571 if ( strpos( $dir, 'ssl/keys' ) !== false ) {
1572 foreach ( glob( $dir . '/*' ) as $file ) {
1573 if ( is_dir( $file ) ) {
1574 $this->delete_files_directories_recursively( $file );
1575 } else {
1576 unlink( $file );
1577 }
1578 }
1579 rmdir( $dir );
1580 }
1581 }
1582
1583 public function maybe_create_htaccess_directories() {
1584 if (!rsssl_user_can_manage()) {
1585 return;
1586 }
1587
1588 if ( !RSSSL()->server->uses_htaccess() ) {
1589 return;
1590 }
1591
1592 if ( !get_option('rsssl_create_folders_in_root') ) {
1593 return;
1594 }
1595
1596 if ( !empty($this->get_directory_path()) ) {
1597 $this->write_htaccess_dir_file( $this->get_directory_path().'ssl/.htaccess' ,'ssl');
1598 }
1599
1600 if ( !empty($this->key_directory()) ) {
1601 $this->write_htaccess_dir_file( trailingslashit($this->key_directory()).'.htaccess' ,'key');
1602 }
1603 if ( !empty($this->certs_directory()) ) {
1604 $this->write_htaccess_dir_file( trailingslashit($this->certs_directory()).'.htaccess' ,'certs');
1605 }
1606 }
1607
1608 public function write_htaccess_dir_file($path, $type){
1609 $htaccess = '<ifModule mod_authz_core.c>' . "\n"
1610 . ' Require all denied' . "\n"
1611 . '</ifModule>' . "\n"
1612 . '<ifModule !mod_authz_core.c>' . "\n"
1613 . ' Deny from all' . "\n"
1614 . '</ifModule>';
1615 insert_with_markers($path, 'Really Simple Security LETS ENCRYPT', $htaccess);
1616
1617 $htaccess = file_get_contents( $path );
1618 if ( stripos($htaccess, 'Require all denied') !== FALSE ) {
1619 update_option('rsssl_htaccess_file_set_'.$type, true, false);
1620 return;
1621 }
1622 }
1623
1624 /**
1625 * Check if it's a subdomain multisite
1626 * @return RSSSL_RESPONSE
1627 */
1628 public function is_subdomain_setup(){
1629 if ( !is_multisite() ) {
1630 $is_subdomain = false;
1631 } else {
1632 if ( defined('SUBDOMAIN_INSTALL') && SUBDOMAIN_INSTALL ) {
1633 $is_subdomain = true;
1634 } else {
1635 $is_subdomain = false;
1636 }
1637 }
1638
1639 if ($is_subdomain) {
1640 $status = 'error';
1641 $action = 'stop';
1642 $message = sprintf(__("This is a multisite configuration with subdomains. You should generate a wildcard certificate on the root domain.",'really-simple-ssl'), '<a href="'.rsssl_link('pro','error', 'letsencrypt').'" target="_blank">','</a>');
1643 rsssl_progress_remove('system-status');
1644 } else {
1645 $status = 'success';
1646 $action = 'continue';
1647 $message = __("No subdomain setup detected.","really-simple-ssl");
1648 }
1649
1650 return new RSSSL_RESPONSE($status, $action, $message);
1651 }
1652
1653 /**
1654 * Check if we're about to create a wilcard certificate
1655 * @return bool
1656 */
1657
1658 public function is_wildcard(){
1659 $subjects = $this->get_subjects();
1660 $is_wildcard = false;
1661 foreach ($subjects as $domain ) {
1662 if ( strpos($domain, '*') !== false ) {
1663 $is_wildcard = true;
1664 }
1665 }
1666
1667 return $is_wildcard;
1668 }
1669
1670 /**
1671 * Check if the alias domain is available
1672 *
1673 * @return RSSSL_RESPONSE
1674 */
1675 public function alias_domain_available(){
1676 if ( rsssl_is_subdomain() ) {
1677 return new RSSSL_RESPONSE('success', 'continue',__("Alias domain check is not relevant for a subdomain","really-simple-ssl"));
1678 }
1679 //write a test file to the uploads directory
1680 $uploads = wp_upload_dir();
1681 $upload_dir = trailingslashit($uploads['basedir']);
1682 $upload_url = trailingslashit($uploads['baseurl']);
1683 $file_content = false;
1684 $status_code = __('no response','really-simple-ssl');
1685 $domain = rsssl_get_domain();
1686
1687 if ( strpos( $domain, 'www.' ) !== false ) {
1688 $is_www = true;
1689 $alias_domain = str_replace( 'www.', '', $domain );
1690 } else {
1691 $is_www = false;
1692 $alias_domain = 'www.'.$domain;
1693 }
1694
1695 if ( $is_www ) {
1696 $message = __("Please check if the non www version of your site also points to this website.", "really-simple-ssl" );
1697 } else {
1698 $message = __("Please check if the www version of your site also points to this website.", "really-simple-ssl" );
1699 }
1700 $error_message = __( "Could not verify alias domain.", "really-simple-ssl") .' '. $message.' '. __( "If this is not the case, don't add this alias to your certificate.", "really-simple-ssl");
1701 //get cached status first.
1702 $cached_status = get_transient('rsssl_alias_domain_available');
1703 if ( $cached_status ) {
1704 if ( $cached_status === 'available' ) {
1705 $status = 'success';
1706 $action = 'continue';
1707 $message = __( "Successfully verified alias domain.", "really-simple-ssl" );
1708 } else {
1709 $status = 'warning';
1710 $action = 'continue';
1711 $message = $error_message;
1712 }
1713 return new RSSSL_RESPONSE($status, $action, $message);
1714 }
1715
1716 if ( ! file_exists( $upload_dir . 'rsssl' ) ) {
1717 mkdir( $upload_dir . 'rsssl', 0755 );
1718 }
1719
1720 $test_string = 'file to test alias domain existence';
1721 $test_file = $upload_dir . 'rsssl/test.txt';
1722 file_put_contents($test_file, $test_string );
1723 $test_url = $upload_url . 'rsssl/test.txt';
1724
1725
1726 if ( ! file_exists( $test_file ) ) {
1727 $status = 'error';
1728 $action = 'stop';
1729 $message = __("Could not create test folder and file.", "really-simple-ssl").' '.
1730 __("Please create a folder 'rsssl' in the uploads directory, with 644 permissions.", "really-simple-ssl");
1731 } else {
1732 set_transient('rsssl_alias_domain_available', 'not-available', 30 * MINUTE_IN_SECONDS );
1733 $alias_test_url = str_replace( $domain, $alias_domain, $test_url );
1734 //always over http:
1735 $alias_test_url = str_replace('https://','http://', $alias_test_url);
1736 $response = wp_remote_get( $alias_test_url );
1737 if ( is_array( $response ) ) {
1738 $status_code = wp_remote_retrieve_response_code( $response );
1739 $file_content = wp_remote_retrieve_body( $response );
1740 }
1741
1742 if ( $status_code !== 200 ) {
1743 $status = 'warning';
1744 $action = 'continue';
1745 $message = $error_message;
1746 if (intval($status_code) != 0 ) {
1747 $message .= ' '.sprintf( __( "Error code %s", "really-simple-ssl" ), $status_code );
1748 }
1749 } else {
1750 if ( ! is_wp_error( $response ) && ( strpos( $file_content, $test_string ) !== false ) ) {
1751 //make sure we only set this value once, during first setup.
1752 if ( !get_option('rsssl_initial_alias_domain_value_set') ) {
1753 rsssl_update_option('include_alias', true);
1754 update_option('rsssl_initial_alias_domain_value_set', true, false);
1755 }
1756 $status = 'success';
1757 $action = 'continue';
1758 $message = __( "Successfully verified alias domain.", "really-simple-ssl" );
1759 set_transient('rsssl_alias_domain_available', 'available', 30 * MINUTE_IN_SECONDS );
1760 } else {
1761 $status = 'warning';
1762 $action = 'continue';
1763 $message = $error_message;
1764 }
1765 }
1766 }
1767
1768 return new RSSSL_RESPONSE($status, $action, $message);
1769 }
1770
1771 /**
1772 * Get string error from error message.
1773 * @param mixed|LE_ACME2\Exception\InvalidResponse $e
1774 *
1775 * @return string
1776 */
1777 private function get_error($e){
1778 $is_raw_response = false;
1779 if (method_exists($e, 'getRawResponse') && isset($e->getRawResponse()->body['detail'])) {
1780 $is_raw_response = true;
1781 $error = $e->getRawResponse()->body['detail'];
1782 //check for subproblems
1783 if (isset($e->getRawResponse()->body['subproblems'])){
1784 $error .= '<ul>';
1785 foreach($e->getRawResponse()->body['subproblems'] as $index => $problem) {
1786 $error .= '<li>'. $this->cleanup_error_message($e->getRawResponse()->body['subproblems'][$index]['detail']).'</li>';
1787 }
1788 $error .= '</ul>';
1789 }
1790
1791 } else {
1792 $error = $e->getMessage();
1793 }
1794
1795
1796 $max = strpos($error, 'CURL response');
1797 if ($max===false) {
1798 $max = 200;
1799 }
1800 if (!$is_raw_response){
1801 $error = substr( $error, 0, $max);
1802 }
1803 return $error;
1804
1805 }
1806
1807 /**
1808 * Generic SSL cert installation function
1809 *
1810 * @return RSSSL_RESPONSE
1811 */
1812 public function cron_renew_installation() {
1813 $install_method = get_option('rsssl_le_certificate_installed_by_rsssl');
1814 $data = explode(':', $install_method );
1815
1816 $server = isset($data[0]) ? $data[0] : false;
1817 $type = isset($data[1]) ? $data[1] : false;
1818
1819 $attempt_count = (int) get_transient( 'rsssl_le_install_attempt_count' );
1820 $attempt_count++;
1821 set_transient('rsssl_le_install_attempt_count', $attempt_count, DAY_IN_SECONDS);
1822 if ( $attempt_count>10 ){
1823 delete_option("rsssl_le_start_installation");
1824 $status = 'error';
1825 $action = 'stop';
1826 $message = __("The certificate installation was rate limited. Please try again later.",'really-simple-ssl');
1827 return new RSSSL_RESPONSE($status, $action, $message);
1828 }
1829
1830 if (rsssl_is_ready_for('installation')) {
1831 try {
1832 if ( $server === 'cpanel' ) {
1833 if ($type==='default') {
1834 $response = rsssl_install_cpanel_default();
1835 } else if ( function_exists('rsssl_shell_installSSL') ) {
1836 $response = rsssl_shell_installSSL();
1837 } else {
1838 //in case of auto ssl.
1839 $response = new RSSSL_RESPONSE('error', 'stop', '');
1840 delete_option( "rsssl_le_start_installation" );
1841 }
1842
1843 if ( $response->status === 'success' ) {
1844 delete_option( "rsssl_le_start_installation" );
1845 }
1846 return $response;
1847 } else if ( $server === 'plesk') {
1848 $response = rsssl_plesk_install();
1849 if ( $response->status === 'success' ) {
1850 delete_option( "rsssl_le_start_installation" );
1851 }
1852 return $response;
1853 } else {
1854 $status = 'error';
1855 $action = 'stop';
1856 $message = __("Not recognized server.", "really-simple-ssl");
1857 }
1858 } catch (Exception $e) {
1859 $status = 'error';
1860 $action = 'stop';
1861 $message = __("Installation failed.", "really-simple-ssl");
1862 }
1863 } else {
1864 $status = 'error';
1865 $action = 'stop';
1866 $message = __("The system is not ready for the installation yet. Please run the wizard again.", "really-simple-ssl");
1867 }
1868
1869 return new RSSSL_RESPONSE($status, $action, $message);
1870 }
1871
1872 public function upgrade(){
1873 if ( get_option('rsssl_upgrade_le_key') ) {
1874 // Check if the encryption key is not empty before upgrading. On slow servers, the write to wp-config.php can be
1875 // incomplete before the plugin gets here
1876 $key = $this->get_encryption_key();
1877 if ( empty( $key ) ) {
1878 return;
1879 }
1880 delete_option('rsssl_upgrade_le_key');
1881 $site_key = get_site_option( 'rsssl_le_key');
1882 if ( $site_key ) {
1883 $options = [
1884 'directadmin_password',
1885 'cpanel_password',
1886 'cloudways_api_key',
1887 'plesk_password',
1888 ];
1889 foreach ( $options as $option ) {
1890 $option_value = rsssl_get_option( $option );
1891 if ( $option_value ) {
1892 $decrypted = $this->decrypt_if_prefixed( $option_value, 'rsssl_', $site_key);
1893 $encrypted = $this->encrypt_with_prefix($decrypted);
1894 rsssl_update_option($option, $encrypted);
1895 }
1896 }
1897 delete_site_option( 'rsssl_le_key');
1898 }
1899 }
1900 }
1901
1902 /**
1903 * Cleanup the default message a bit
1904 *
1905 * @param $msg
1906 *
1907 * @return string|string[]
1908 */
1909 private function cleanup_error_message($msg){
1910 return str_replace(array(
1911 'Refer to sub-problems for more information.',
1912 'Error creating new order ::',
1913 ), '', $msg);
1914 }
1915 }