Running the fork against a live WordPress found a real hole in registration, and it is the kind that only shows up when you actually try it. ECDSA public-key recovery always succeeds. Given any well-formed signature and any digest it returns a key — just not the signer's, unless the digest is the one that was signed. The auth flow leaned on that as if a wrong message would fail. It does not; it quietly yields a stranger's address. At sign-in this was harmless, because the wrong address matches no account and the attempt fails. Registration and wallet-linking were another matter: both took the recovered address and bound it to an account, so a signature over slightly different text — a challenge copied without its blank line, a wallet that rewrote the text, a login signature replayed at the registration form — created an account keyed to an address nobody could sign for. The person would see "success" and discover the truth the next time they tried to get in. Wallet-linking was worse still: it would move an existing account onto a dead address and lock its owner out of their own site. Both paths now require the address the signer claims and compare it to the recovered one, which is what verification actually means. Sign-in accepts the claim when the page sends it and uses it to turn "no account uses that wallet" into the more useful "that signature is not over the text we asked for". Also from running it: URL rewriting mangled every link on a site whose URL carries a port. The protocol-relative pass matched inside absolute URLs and gave each one a second scheme, and matching the host without its port left the port stranded as `//host:8760:8760/`. Local and staging installs would have exported a site of broken links. Plain permalinks silently collapse an entire site onto one exported file, because every post's URL is `/?p=N` and its path is `/`. The queue looks healthy the whole time. The Publishing screen now says so. Translations loaded on `plugins_loaded`, which WordPress 6.7 warns about on every request — the kind of noise that trains people to stop reading logs. And one deletion: an `is_email()` filter written on the assumption that WordPress rejects `.invalid` addresses. It does not — `is_email()` validates syntax, not whether a domain could exist — so the filter never fired. A filter that appears to relax a rule but does not is worse than no filter, because someone later reasons from it. The documentation made the same claim and has been corrected. Verification added rather than asserted: tests/live.mjs drives a real instance over HTTP (40 checks), and tests/mock-gateway.mjs answers uploads with the signature check transcribed from the gateway's own source, so the publishing path can be exercised without a registered name.
178 lines
6.8 KiB
PHP
178 lines
6.8 KiB
PHP
<?php
|
|
/**
|
|
* Plugin Name: Sirius Press Compatibility
|
|
* Plugin URI: https://code.silentmode.st/silentmode/sirius-press
|
|
* Description: Keeps plugins that hard-require an administrator email address installable and activatable on a site that has none.
|
|
* Version: 0.1.0
|
|
* Requires at least: 6.5
|
|
* Requires PHP: 7.4
|
|
* Requires Plugins: sirius-press-core
|
|
* Author: Silent Mode
|
|
* Author URI: https://silentmode.st
|
|
* License: GPL-2.0-or-later
|
|
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
|
* Text Domain: sirius-press
|
|
*
|
|
* @package SiriusPress
|
|
*
|
|
* ---------------------------------------------------------------------------
|
|
*
|
|
* A large part of the WordPress ecosystem assumes `get_option('admin_email')`
|
|
* returns something, and a smaller but important part refuses to finish its
|
|
* setup wizard if it does not. WooCommerce wants a store address for order
|
|
* emails. Contact Form 7 defaults a form's "To" field to it. Yoast reads it
|
|
* while building schema. None of these are wrong to do so — they were written
|
|
* for a WordPress where that value always exists.
|
|
*
|
|
* Rather than patch each of them, this plugin makes the assumption true: the
|
|
* site always has an admin email, and it is permanently undeliverable. The
|
|
* `.invalid` top-level domain is reserved by RFC 2606 for exactly this — it
|
|
* is guaranteed never to resolve, anywhere, ever. So a plugin that stores it,
|
|
* validates it or puts it in a "From" header behaves normally, and a plugin
|
|
* that tries to *send* to it has its message caught by the Sirius Press inbox
|
|
* before it reaches a mail transport.
|
|
*
|
|
* What this plugin deliberately does NOT do is disable `wp_mail()`. A site
|
|
* owner who configures SMTP gets working contact forms and working order
|
|
* receipts, because those go to real addresses that real people gave. The
|
|
* fork's objection was never to email as a feature — only to email as an
|
|
* identity.
|
|
*/
|
|
|
|
defined( 'ABSPATH' ) || exit;
|
|
|
|
define( 'SIRIUS_PRESS_COMPAT_VERSION', '0.1.0' );
|
|
|
|
/**
|
|
* Supply a placeholder wherever WordPress or a plugin reads the admin email.
|
|
*
|
|
* Filtered rather than written to the database so that a site owner who does
|
|
* set a real address keeps it: the filter defers to any non-empty stored
|
|
* value and only fills a gap.
|
|
*/
|
|
function sirius_press_admin_email( $value ) {
|
|
if ( is_string( $value ) && '' !== trim( $value ) ) {
|
|
return $value;
|
|
}
|
|
return 'admin@' . SP_Settings::stub_email_domain();
|
|
}
|
|
add_filter( 'option_admin_email', 'sirius_press_admin_email' );
|
|
add_filter( 'default_option_admin_email', 'sirius_press_admin_email' );
|
|
add_filter( 'pre_option_new_admin_email', '__return_empty_string' );
|
|
|
|
/**
|
|
* Fill a blank `user_email` on save rather than letting one persist.
|
|
*
|
|
* `$user->user_email` is read in thousands of places across the ecosystem,
|
|
* usually without a null check, so an account with an empty one leaves
|
|
* warnings and blank "From" headers scattered through other people's code.
|
|
* An account created before this fork was installed, or by a plugin calling
|
|
* `wp_insert_user()` directly, can have exactly that.
|
|
*
|
|
* Runs late so a plugin that supplies a real address wins.
|
|
*/
|
|
add_filter(
|
|
'wp_pre_insert_user_data',
|
|
function ( $data, $update, $user_id ) {
|
|
if ( ! class_exists( 'SP_Identity' ) ) {
|
|
return $data;
|
|
}
|
|
if ( ! empty( $data['user_email'] ) ) {
|
|
return $data;
|
|
}
|
|
$address = $user_id ? SP_Identity::address_of( (int) $user_id ) : '';
|
|
$data['user_email'] = SP_Identity::stub_email( $address );
|
|
return $data;
|
|
},
|
|
20,
|
|
3
|
|
);
|
|
|
|
/**
|
|
* Stop core nagging about an unconfirmed administrator email change.
|
|
*
|
|
* The "Your admin email is still <x>, please confirm" prompt sends a
|
|
* confirmation link to an address that cannot receive it, which makes the
|
|
* prompt permanent and unactionable.
|
|
*/
|
|
add_filter( 'admin_email_check_interval', '__return_zero' );
|
|
|
|
/**
|
|
* WooCommerce.
|
|
*
|
|
* Its setup wizard and its system-status report both read the store address
|
|
* out of `woocommerce_email_from_address`, and the onboarding profiler posts
|
|
* to a remote service with it. Supplying the placeholder lets the wizard
|
|
* finish; order emails to real customers are unaffected, because those use
|
|
* the address the customer typed at checkout.
|
|
*/
|
|
add_filter(
|
|
'pre_option_woocommerce_email_from_address',
|
|
function ( $value ) {
|
|
if ( ! empty( $value ) || ! class_exists( 'SP_Settings' ) ) {
|
|
return $value;
|
|
}
|
|
return 'shop@' . SP_Settings::stub_email_domain();
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Contact Form 7.
|
|
*
|
|
* CF7 validates a form's mail template on save and marks the form as
|
|
* misconfigured if the "To" address is empty, which it will be on a site with
|
|
* no admin email. The placeholder makes new forms save cleanly; a site owner
|
|
* who wants the form to actually deliver replaces it with a real address and
|
|
* configures SMTP, which is documented in docs/smtp.md.
|
|
*/
|
|
add_filter(
|
|
'wpcf7_default_template',
|
|
function ( $template, $prop ) {
|
|
if ( 'mail' !== $prop || ! is_array( $template ) || ! class_exists( 'SP_Settings' ) ) {
|
|
return $template;
|
|
}
|
|
if ( empty( $template['recipient'] ) ) {
|
|
$template['recipient'] = 'admin@' . SP_Settings::stub_email_domain();
|
|
}
|
|
return $template;
|
|
},
|
|
10,
|
|
2
|
|
);
|
|
|
|
/*
|
|
* Note on is_email(): no filter is needed.
|
|
*
|
|
* It would be reasonable to assume WordPress rejects a `.invalid` address,
|
|
* since RFC 2606 reserves that TLD precisely so it can never resolve — and an
|
|
* earlier version of this file carried a filter to force such addresses
|
|
* through on that assumption. The assumption is wrong. `is_email()` validates
|
|
* syntax, not whether a domain could ever exist, so `noreply@….invalid`
|
|
* already passes and the filter never fired.
|
|
*
|
|
* Leaving it in would have been worse than useless: a filter that appears to
|
|
* relax a validation rule, but does not, is exactly the kind of thing someone
|
|
* later reasons from. Verified against WordPress 7.1.1.
|
|
*/
|
|
|
|
/**
|
|
* A short explanation on the plugins screen, next to anything known to want
|
|
* an address. Better a sentence here than a confused hour later.
|
|
*/
|
|
add_action(
|
|
'after_plugin_row',
|
|
function ( $plugin_file ) {
|
|
static $notes = array(
|
|
'woocommerce/woocommerce.php' => 'WooCommerce will work, including checkout. Order emails need SMTP — see docs/smtp.md.',
|
|
'contact-form-7/wp-contact-form-7.php' => 'Forms save and submit. Delivery needs SMTP and a real recipient address — see docs/smtp.md.',
|
|
'wordpress-seo/wp-seo.php' => 'Yoast works unchanged. Its sitemaps are exported to your name along with everything else.',
|
|
);
|
|
if ( ! isset( $notes[ $plugin_file ] ) || ! is_plugin_active( $plugin_file ) ) {
|
|
return;
|
|
}
|
|
printf(
|
|
'<tr class="plugin-update-tr active"><td colspan="4" class="plugin-update"><div class="update-message notice inline notice-info notice-alt"><p>%s</p></div></td></tr>',
|
|
esc_html( $notes[ $plugin_file ] )
|
|
);
|
|
}
|
|
);
|