PluginProbe ʕ •ᴥ•ʔ
SiteGuard WP Plugin / trunk
SiteGuard WP Plugin vtrunk
1.8.9 1.8.8 1.8.7 1.8.6 1.8.6-beta1 1.8.6-beta2 1.8.4 1.8.5 1.8.3 1.8.2 1.8.1 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.2.0 1.2.1 1.2.2 1.2.3 1.4.3 1.5.0 1.5.1 1.5.2 1.6.0 1.6.1 1.7.0 1.7.1 1.7.10 1.7.11 1.7.12 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.0-beta1 1.8.0-beta2 1.8.0-beta3 1.8.0-beta4
siteguard / siteguard.php
siteguard Last commit date
admin 1 week ago classes 1 week ago css 2 months ago images 2 months ago languages 1 week ago really-simple-captcha 1 week ago changelog.txt 2 months ago license.txt 11 years ago readme.txt 1 week ago siteguard.php 1 week ago uninstall.php 1 week ago
siteguard.php
527 lines
1 <?php
2 /*
3 Plugin Name: SiteGuard WP Plugin
4 Plugin URI: https://www.jp-secure.com/siteguard_wp_plugin_en/
5 Description: Adds WordPress login and admin protections, including CAPTCHA, login lock, login alerts, renamed login URLs, and SiteGuard WAF tuning support.
6 Author: JP-Secure
7 Author URI: https://www.eg-secure.co.jp/
8 Text Domain: siteguard
9 Domain Path: /languages/
10 Version: 1.8.9
11 */
12
13 /*
14 Copyright 2014 EG Secure Solutions Inc (JP-Secure Inc)
15
16 This program is free software; you can redistribute it and/or modify
17 it under the terms of the GNU General Public License, version 2, as
18 published by the Free Software Foundation.
19
20 This program is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
24
25 You should have received a copy of the GNU General Public License
26 along with this program; if not, write to the Free Software
27 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
28 */
29
30 if ( ! defined( 'ABSPATH' ) ) {
31 exit;
32 }
33
34 $data = get_file_data( __FILE__, array( 'version' => 'Version' ) );
35 define( 'SITEGUARD_VERSION', $data['version'] );
36
37 define( 'SITEGUARD_PATH', plugin_dir_path( __FILE__ ) );
38 define( 'SITEGUARD_URL_PATH', plugin_dir_url( __FILE__ ) );
39
40 define( 'SITEGUARD_RENAME_MODE_HTACCESS', '0');
41 define( 'SITEGUARD_RENAME_MODE_STUB', '1');
42
43 define( 'SITEGUARD_LOGIN_NOSELECT', 4 );
44 define( 'SITEGUARD_LOGIN_SUCCESS', 0 );
45 define( 'SITEGUARD_LOGIN_FAILED', 1 );
46 define( 'SITEGUARD_LOGIN_FAIL_ONCE', 2 );
47 define( 'SITEGUARD_LOGIN_LOCKED', 3 );
48
49 define( 'SITEGUARD_LOGIN_TYPE_NOSELECT', 2 );
50 define( 'SITEGUARD_LOGIN_TYPE_NORMAL', 0 );
51 define( 'SITEGUARD_LOGIN_TYPE_XMLRPC', 1 );
52
53 require_once 'classes/siteguard-base.php';
54 require_once 'classes/siteguard-config.php';
55 require_once 'classes/siteguard-htaccess.php';
56 require_once 'classes/siteguard-admin-filter.php';
57 require_once 'classes/siteguard-rename-login.php';
58 require_once 'classes/siteguard-login-history.php';
59 require_once 'classes/siteguard-login-lock.php';
60 require_once 'classes/siteguard-login-alert.php';
61 require_once 'classes/siteguard-captcha.php';
62 require_once 'classes/siteguard-disable-xmlrpc.php';
63 require_once 'classes/siteguard-disable-pingback.php';
64 require_once 'classes/siteguard-disable-author-query.php';
65 require_once 'classes/siteguard-waf-exclude-rule.php';
66 require_once 'classes/siteguard-updates-notify.php';
67 require_once 'admin/siteguard-menu-init.php';
68
69 global $siteguard_htaccess;
70 global $siteguard_config;
71 global $siteguard_admin_filter;
72 global $siteguard_rename_login;
73 global $siteguard_loginlock;
74 global $siteguard_loginalert;
75 global $siteguard_captcha;
76 global $siteguard_login_history;
77 global $siteguard_xmlrpc;
78 global $siteguard_pingback;
79 global $siteguard_author_query;
80 global $siteguard_waf_exclude_rule;
81 global $siteguard_updates_notify;
82
83 $siteguard_htaccess = new SiteGuard_Htaccess();
84 $siteguard_config = new SiteGuard_Config();
85 $siteguard_admin_filter = new SiteGuard_AdminFilter();
86 $siteguard_rename_login = new SiteGuard_RenameLogin();
87 $siteguard_loginlock = new SiteGuard_LoginLock();
88 $siteguard_loginalert = new SiteGuard_LoginAlert();
89 $siteguard_login_history = new SiteGuard_LoginHistory();
90 $siteguard_captcha = new SiteGuard_CAPTCHA();
91 $siteguard_xmlrpc = new SiteGuard_Disable_XMLRPC();
92 $siteguard_pingback = new SiteGuard_Disable_Pingback();
93 $siteguard_author_query = new SiteGuard_Disable_Author_Query();
94 $siteguard_waf_exclude_rule = new SiteGuard_WAF_Exclude_Rule();
95 $siteguard_updates_notify = new SiteGuard_UpdatesNotify();
96
97 function siteguard_activate() {
98 global $siteguard_config, $siteguard_admin_filter, $siteguard_rename_login, $siteguard_login_history, $siteguard_captcha, $siteguard_loginlock, $siteguard_loginalert, $siteguard_xmlrpc, $siteguard_pingback, $siteguard_author_query, $siteguard_waf_exclude_rule, $siteguard_updates_notify;
99
100 // Whether this is a first-time install, decided before the first update()
101 // below creates the option. See the version bookkeeping at the end.
102 $is_fresh_install = ! is_array( get_option( 'siteguard_config' ) );
103
104 load_plugin_textdomain(
105 'siteguard',
106 false,
107 dirname( plugin_basename( __FILE__ ) ) . '/languages'
108 );
109
110 $siteguard_config->set( 'show_admin_notices', '0' );
111 $siteguard_config->update();
112 $siteguard_admin_filter->init();
113 $siteguard_rename_login->init();
114 $siteguard_login_history->init();
115 $siteguard_captcha->init();
116 $siteguard_loginlock->init();
117 $siteguard_loginalert->init();
118 $siteguard_xmlrpc->init();
119 $siteguard_pingback->init();
120 $siteguard_author_query->init();
121 $siteguard_waf_exclude_rule->init();
122 $siteguard_updates_notify->init();
123
124 if ( $is_fresh_install ) {
125 // One piece of 1.7.x state does outlive the plugin: its .htaccess blocks.
126 // Deleting the plugin through WordPress runs the deactivation hook, which
127 // clears them, but a directory removed by hand (FTP) leaves them in place,
128 // and the Admin Filter block ("RewriteRule ^wp-admin 404-siteguard") locks
129 // administrators out of /wp-admin/. upgrade() takes care of this for an
130 // in-place update; a fresh install skips upgrade() entirely because of the
131 // version recorded below, so the same cleanup has to happen here.
132 // clear_settings() does nothing when the mark is absent.
133 SiteGuard_Htaccess::clear_settings( $siteguard_admin_filter->get_mark() );
134 SiteGuard_Htaccess::clear_settings( $siteguard_xmlrpc->get_mark() );
135
136 // Record the version now. Every migration block in upgrade() repairs
137 // state left by an older release, and the init() calls above have just
138 // built the current state from scratch, so there is nothing to migrate.
139 //
140 // Without this the stored version stays empty (treated as 0.0.0) and
141 // upgrade() runs on every request until one of them finishes, calling
142 // SiteGuard_RenameLogin::feature_on() — and its loopback .htaccess
143 // self-test — many times in parallel right after activation.
144 $siteguard_config->set( 'version', SITEGUARD_VERSION );
145 $siteguard_config->update();
146 }
147 }
148 register_activation_hook( __FILE__, 'siteguard_activate' );
149
150 function siteguard_deactivate() {
151 global $siteguard_config;
152 $siteguard_config->set( 'show_admin_notices', '0' );
153 $siteguard_config->update();
154 SiteGuard_RenameLogin::feature_off();
155 SiteGuard_AdminFilter::feature_off();
156 SiteGuard_Disable_XMLRPC::feature_off();
157 SiteGuard_WAF_Exclude_Rule::feature_off();
158 SiteGuard_UpdatesNotify::feature_off();
159 }
160 register_deactivation_hook( __FILE__, 'siteguard_deactivate' );
161
162
163 class SiteGuard extends SiteGuard_Base {
164 const UPGRADE_LOCK_TRANSIENT = 'siteguard_upgrade_lock';
165
166 protected $menu_init;
167 function __construct() {
168 global $siteguard_config;
169 add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ) );
170 $this->htaccess_check();
171 // upgrade() must run on every request, not only admin_init, so that
172 // upgrades from 1.7.x can clean up legacy .htaccess blocks even when
173 // /wp-admin/ would otherwise be locked out by those very rules.
174 add_action( 'init', array( $this, 'upgrade' ), 0 );
175 if ( is_admin() ) {
176 include 'admin/siteguard-menu-login-history.php';
177 $this->menu_init = new SiteGuard_Menu_Init();
178 add_action( 'init', array( $this, 'set_cookie' ) );
179 if ( '0' === $siteguard_config->get( 'show_admin_notices' ) && '1' === $siteguard_config->get( 'renamelogin_enable' ) ) {
180 add_action( 'admin_notices', array( $this, 'admin_notices' ) );
181 $siteguard_config->set( 'show_admin_notices', '1' );
182 $siteguard_config->update();
183 }
184 }
185 }
186 function set_cookie() {
187 SiteGuard_Menu_Login_History::set_cookie();
188 }
189 function plugins_loaded() {
190 load_plugin_textdomain(
191 'siteguard',
192 false,
193 dirname( plugin_basename( __FILE__ ) ) . '/languages'
194 );
195 }
196 function htaccess_check() {
197 global $siteguard_config, $siteguard_rename_login;
198
199 // A self-test request is the plugin looking at itself mid-rebuild, so it
200 // must not judge the .htaccess state at all.
201 //
202 // The other mid-rebuild case — feature_on() having removed the block it
203 // is about to write back — is checked in rename_rebuild_in_progress()
204 // below, at the point where something would actually be changed. Reading
205 // that transient here instead would cost two option lookups on every
206 // single request just to confirm that nothing is wrong.
207 if ( siteguard_is_self_test_request() ) {
208 return;
209 }
210
211 // Only check whether the SiteGuard marker block still exists in .htaccess.
212 // The actual ".htaccess effectiveness" probe (test_htaccess) is performed
213 // only when the user toggles a feature on, to avoid loopback HTTP
214 // requests on every WordPress request.
215 if ( '1' === $siteguard_config->get( 'waf_exclude_rule_enable' ) ) {
216 if ( ! SiteGuard_Htaccess::is_exists_setting( SiteGuard_WAF_Exclude_Rule::get_mark() ) ) {
217 $siteguard_config->set( 'waf_exclude_rule_enable', '0' );
218 $siteguard_config->update();
219 }
220 }
221 if ( '1' === $siteguard_config->get( 'renamelogin_enable' ) ) {
222 // Act only on a mode that was actually recorded. "renamelogin_stub"
223 // arrived in 1.8.0, and nothing in upgrade() backfills it — the only
224 // migration that would (via feature_on()) is gated on < 1.2.5 — so an
225 // install updated from 1.7.x keeps it unset until an administrator
226 // saves the Rename Login screen. That install is working: it is served
227 // by the .htaccess block 1.7.x wrote, and is_stub_mode() reads the
228 // unset value as "not stub", so the URLs handed out match.
229 //
230 // Treating the unset value as stub mode here would make the branch
231 // below "repair" that healthy install — write a stub file, then delete
232 // the block that is actually serving the login page — and no later
233 // migration would undo it.
234 $mode = $siteguard_config->get( 'renamelogin_stub' );
235 if ( SITEGUARD_RENAME_MODE_HTACCESS === $mode ) {
236 if ( ! SiteGuard_Htaccess::is_exists_setting( SiteGuard_RenameLogin::get_mark() )
237 && ! $this->rename_rebuild_in_progress()
238 ) {
239 $siteguard_config->set( 'renamelogin_enable', '0' );
240 $siteguard_config->update();
241 }
242 } elseif ( SITEGUARD_RENAME_MODE_STUB === $mode ) {
243 // Stub (.php) mode. Restore the agreement between what is
244 // recorded, what the server does and which files exist:
245 // concurrent feature_on() runs could leave any combination
246 // behind (each one starts by deleting both the .htaccess block
247 // and the current stub file before deciding again), and the stub
248 // file can also go missing on its own, e.g. removed by an
249 // administrator who did not recognise it in the site root.
250 $stub_exists = file_exists( $siteguard_rename_login->stub_abspath() );
251 $block_exists = $siteguard_rename_login->can_use_htaccess()
252 && SiteGuard_Htaccess::is_exists_setting( SiteGuard_RenameLogin::get_mark() );
253
254 // The healthy shape of stub mode: the stub is there and no
255 // leftover block. Both have to be looked at to know that — the
256 // "leftover block" case is exactly the one where the stub is
257 // present too — so the reads above cannot be skipped; on Nginx
258 // can_use_htaccess() returns before touching the file.
259 if ( $stub_exists && ! $block_exists ) {
260 return;
261 }
262 if ( $this->rename_rebuild_in_progress() ) {
263 return;
264 }
265
266 // Put the stub back first. It is the entry point the recorded
267 // settings advertise, and it has to exist before the .htaccess
268 // block — which may be the only one working right now — is
269 // taken away.
270 if ( ! $stub_exists ) {
271 $stub_exists = $siteguard_rename_login->ensure_stub();
272 }
273
274 if ( $block_exists && $stub_exists ) {
275 // The block rewrites "<slug>(.*)" to "wp-login.php$1", so the
276 // "<slug>.php" URL shown on the settings screen turns into
277 // "wp-login.php.php" and returns 404 — while the login form
278 // rendered at the extensionless URL posts to that same dead
279 // ".php" address. Dropping the block leaves the stub serving
280 // the URL that is actually advertised.
281 //
282 // Converging on the recorded mode is also the only safe move
283 // when it cannot be told whether the block does anything:
284 // can_use_htaccess() only knows that this is Apache and that
285 // the file is writable, not whether the server reads it at
286 // all (AllowOverride None). Keeping the block and switching
287 // the recorded mode to match it would, in that case, point
288 // the settings screen at a URL that nothing serves.
289 SiteGuard_Htaccess::clear_settings( SiteGuard_RenameLogin::get_mark() );
290 } elseif ( $block_exists ) {
291 // The stub could not be written (a read-only site root), so
292 // the .htaccess block is the only way in that is left.
293 // Record the mode that matches it rather than removing it.
294 //
295 // A block in .htaccess is good evidence that .htaccess works
296 // here: feature_on() only writes one after its self-test has
297 // passed. That is why this is preferred over turning the
298 // feature off — it keeps a login URL that is very likely
299 // serving, instead of exposing wp-login.php again.
300 $siteguard_config->set( 'renamelogin_stub', SITEGUARD_RENAME_MODE_HTACCESS );
301 $siteguard_config->set( 'renamelogin_stub_reason', array() );
302 $siteguard_config->update();
303 } elseif ( ! $stub_exists ) {
304 // Neither entry point exists and the stub cannot be written
305 // (a read-only site root): nothing serves the login page at
306 // all, and retrying the same failing write on every request
307 // would never change that. Hand the login page back to
308 // wp-login.php, exactly as the .htaccess branch above does
309 // when its block has gone missing — being able to log in
310 // matters more than keeping the URL hidden.
311 // maybe_notice_stub_failed() explains the cause once an
312 // administrator is back in.
313 siteguard_error_log( 'Rename Login turned off: the stub file is missing and cannot be created.' );
314 $siteguard_config->set( 'renamelogin_enable', '0' );
315 $siteguard_config->update();
316 }
317 }
318 }
319 }
320 /**
321 * Whether SiteGuard_RenameLogin::feature_on() is rebuilding the .htaccess
322 * block right now. Between its clear_settings() and update_settings() the
323 * block is legitimately absent, and a request landing in that window must
324 * not read that as "the feature is broken".
325 *
326 * Only called once a discrepancy has been seen, so the option lookups stay
327 * off the path of ordinary requests.
328 *
329 * @return bool
330 */
331 private function rename_rebuild_in_progress() {
332 return (bool) get_transient( SiteGuard_RenameLogin::HTACCESS_REBUILD_TRANSIENT );
333 }
334
335 function admin_notices() {
336 global $siteguard_rename_login;
337 echo '<div class="updated" style="background-color:#719f1d;"><p><span style="border: 4px solid #def1b8;padding: 4px 4px;color:#fff;font-weight:bold;background-color:#038bc3;">';
338 echo esc_html__( 'The login page URL has been changed.', 'siteguard' ) . '</span>';
339 printf(
340 '<span style="color:#eee;">'
341 . esc_html__( 'Please bookmark the %1$s. You can change this setting %2$s.', 'siteguard' )
342 . '</span></p></div>',
343 '<a style="color:#fff;text-decoration:underline;" href="' . esc_url( wp_login_url() ) . '">' . esc_html__( 'new login URL', 'siteguard' ) . '</a>',
344 '<a style="color:#fff;text-decoration:underline;" href="' . esc_url( menu_page_url( 'siteguard_rename_login', false ) ) . '">' . esc_html__( 'here', 'siteguard' ) . '</a>'
345 );
346 $siteguard_rename_login->send_notify();
347 }
348 function upgrade() {
349 global $siteguard_config, $siteguard_rename_login, $siteguard_admin_filter, $siteguard_loginalert, $siteguard_updates_notify, $siteguard_login_history, $siteguard_xmlrpc, $siteguard_author_query, $siteguard_waf_exclude_rule;
350 $upgrade_ok = true;
351 $old_version = $siteguard_config->get( 'version' );
352 if ( '' === $old_version ) {
353 $old_version = '0.0.0';
354 }
355 if ( $old_version === SITEGUARD_VERSION ) {
356 return;
357 }
358 // The self-test request of an upgrade already in progress. Migrating
359 // from here would start a second upgrade (and a third, and so on: each
360 // self-test that falls through to WordPress boots the plugin again)
361 // before the first one has recorded the new version.
362 if ( siteguard_is_self_test_request() ) {
363 return;
364 }
365 // Advisory lock: the version is only recorded once the migration
366 // finishes, so without it every request that arrives in the meantime
367 // repeats the same work — including feature_on() and its loopback
368 // self-test. It is released below so that a failed upgrade is retried
369 // on the next request as before; the timeout only covers a request
370 // that dies midway.
371 if ( get_transient( self::UPGRADE_LOCK_TRANSIENT ) ) {
372 return;
373 }
374 set_transient( self::UPGRADE_LOCK_TRANSIENT, 1, 5 * MINUTE_IN_SECONDS );
375 if ( version_compare( $old_version, '1.0.6' ) < 0 ) {
376 if ( '1' === $siteguard_config->get( 'admin_filter_enable' ) ) {
377 if ( true !== $siteguard_admin_filter->feature_on( $this->get_ip() ) ) {
378 siteguard_error_log( 'Failed to update at admin_filter from ' . $old_version . ' to ' . SITEGUARD_VERSION . '.' );
379 $upgrade_ok = false;
380 }
381 }
382 }
383 if ( version_compare( $old_version, '1.1.1' ) < 0 ) {
384 $siteguard_loginalert->init();
385 }
386 if ( version_compare( $old_version, '1.2.0' ) < 0 ) {
387 $siteguard_updates_notify->init();
388 }
389 if ( version_compare( $old_version, '1.2.5' ) < 0 ) {
390 if ( '1' === $siteguard_config->get( 'admin_filter_enable' ) ) {
391 $siteguard_admin_filter->cvt_status_for_1_2_5( $this->get_ip() );
392 }
393 if ( '1' === $siteguard_config->get( 'renamelogin_enable' ) ) {
394 if ( true !== $siteguard_rename_login->feature_on() ) {
395 siteguard_error_log( 'Failed to update at rename_login from ' . $old_version . ' to ' . SITEGUARD_VERSION . '.' );
396 $upgrade_ok = false;
397 }
398 }
399 }
400 if ( version_compare( $old_version, '1.3.0' ) < 0 ) {
401 $siteguard_login_history->init();
402 $siteguard_xmlrpc->init();
403 }
404 if ( version_compare( $old_version, '1.5.0' ) < 0 ) {
405 $admin_filter_exclude_path = $siteguard_config->get( 'admin_filter_exclude_path' );
406 if ( false === strpos( $admin_filter_exclude_path, 'site-health.php' ) ) {
407 $admin_filter_exclude_path .= ', site-health.php';
408 $siteguard_config->set( 'admin_filter_exclude_path', $admin_filter_exclude_path );
409 $siteguard_config->update();
410 }
411 }
412 if ( version_compare( $old_version, '1.5.1' ) < 0 ) {
413 if ( '1' === $siteguard_config->get( 'admin_filter_enable' ) ) {
414 if ( true !== $siteguard_admin_filter->feature_on( $this->get_ip() ) ) {
415 siteguard_error_log( 'Failed to update at admin_filter from ' . $old_version . ' to ' . SITEGUARD_VERSION . '.' );
416 $upgrade_ok = false;
417 }
418 }
419 if ( '1' === $siteguard_config->get( 'disable_xmlrpc_enable' ) ) {
420 if ( true !== $siteguard_xmlrpc->feature_on() ) {
421 siteguard_error_log( 'Failed to update at disable_xmlrpc from ' . $old_version . ' to ' . SITEGUARD_VERSION . '.' );
422 $upgrade_ok = false;
423 }
424 }
425 }
426 if ( version_compare( $old_version, '1.6.0' ) < 0 ) {
427 $siteguard_author_query->init();
428 }
429 if ( version_compare( $old_version, '1.7.0' ) < 0 ) {
430 if ( '1' === $siteguard_config->get( 'admin_filter_enable' ) ) {
431 if ( true !== $siteguard_admin_filter->feature_on( $this->get_ip() ) ) {
432 siteguard_error_log( 'Failed to update at admin_filter from ' . $old_version . ' to ' . SITEGUARD_VERSION . '.' );
433 $upgrade_ok = false;
434 }
435 }
436 }
437 if ( version_compare( $old_version, '1.8.0' ) < 0 ) {
438 // Legacy Nginx-exposure cleanup and the rescue_enable default. These
439 // are unrelated to the /wp-admin/ lockout and ran when the install
440 // first reached 1.8.0, so they stay gated on < 1.8.0. The Admin
441 // Filter / XML-RPC .htaccess blocks (which cause the lockout) are
442 // cleared in the < 1.8.3 block below.
443 if ( '' === $siteguard_config->get( 'rescue_enable' ) ) {
444 $siteguard_config->set( 'rescue_enable', '1' );
445 $siteguard_config->update();
446 }
447 // Remove legacy error.log left by previous versions; logging now
448 // uses PHP error_log() so the file would only sit web-exposed on Nginx.
449 $legacy_log = SITEGUARD_PATH . 'error.log';
450 if ( file_exists( $legacy_log ) ) {
451 @unlink( $legacy_log );
452 }
453 // Remove legacy plugin-directory tmp/ used for .htaccess rebuilds.
454 // `clear_settings()` / `update_settings()` now short-circuit on
455 // Nginx (no .htaccess in use), so this directory will not be
456 // recreated there. On Apache it will be regenerated as needed
457 // by make_tmp_dir(). Existing orphan tempnam files would be
458 // web-exposed on Nginx without the .htaccess inside the dir.
459 $legacy_tmp = SITEGUARD_PATH . 'tmp';
460 if ( is_dir( $legacy_tmp ) ) {
461 $entries = @scandir( $legacy_tmp );
462 if ( is_array( $entries ) ) {
463 foreach ( $entries as $entry ) {
464 if ( '.' === $entry || '..' === $entry ) {
465 continue;
466 }
467 $path = $legacy_tmp . DIRECTORY_SEPARATOR . $entry;
468 if ( is_file( $path ) ) {
469 if ( ! @unlink( $path ) ) {
470 @chmod( $path, 0644 );
471 @unlink( $path );
472 }
473 }
474 }
475 }
476 @rmdir( $legacy_tmp );
477 }
478 // Remove legacy CAPTCHA answer files (*.txt). Pre-1.8.0 stored
479 // them at WP_CONTENT_DIR/siteguard/ with an .htaccess block on
480 // .txt; on Nginx that block does not apply and the salt+hash
481 // would be readable. New answer files use .php with a stub
482 // prefix and live in the same directory.
483 $captcha_dir = path_join( WP_CONTENT_DIR, 'siteguard' );
484 if ( is_dir( $captcha_dir ) ) {
485 $entries = @scandir( $captcha_dir );
486 if ( is_array( $entries ) ) {
487 foreach ( $entries as $entry ) {
488 if ( preg_match( '/\.txt$/', $entry ) ) {
489 $path = $captcha_dir . DIRECTORY_SEPARATOR . $entry;
490 if ( is_file( $path ) ) {
491 if ( ! @unlink( $path ) ) {
492 @chmod( $path, 0644 );
493 @unlink( $path );
494 }
495 }
496 }
497 }
498 }
499 }
500 }
501 if ( version_compare( $old_version, '1.8.3' ) < 0 ) {
502 // Admin Page IP Filter and XML-RPC protection moved from .htaccess
503 // to PHP in 1.8.x, leaving their 1.7.x .htaccess blocks orphaned.
504 // The Admin Filter block ("RewriteRule ^wp-admin 404-siteguard")
505 // blocks /wp-admin/ at the Apache layer and can lock administrators
506 // out. clear_settings() is idempotent (a no-op when the mark is
507 // absent), so gate this on the fix release (< 1.8.3) rather than
508 // < 1.8.0: that also recovers the rare install whose stored version
509 // already advanced past 1.8.0 while the block survived (e.g. the
510 // .htaccess was briefly unwritable during the 1.8.0 upgrade).
511 //
512 // Rename Login and WAF Tuning Support still use .htaccess in 1.8.x
513 // with the same mark and block format as 1.7.x, so their blocks are
514 // the current, valid mechanism — they are intentionally NOT touched
515 // here (clearing WAF without a rebuild would drop working rules).
516 SiteGuard_Htaccess::clear_settings( $siteguard_admin_filter->get_mark() );
517 SiteGuard_Htaccess::clear_settings( $siteguard_xmlrpc->get_mark() );
518 }
519 if ( $upgrade_ok && SITEGUARD_VERSION !== $old_version ) {
520 $siteguard_config->set( 'version', SITEGUARD_VERSION );
521 $siteguard_config->update();
522 }
523 delete_transient( self::UPGRADE_LOCK_TRANSIENT );
524 }
525 }
526 $siteguard = new SiteGuard();
527