option = $option; $this->key = (string) $crypto_key; $this->constants = isset( $constants['WPMS_ON'] ) && true === $constants['WPMS_ON'] ? $constants : array(); } /** * Build from this site's option, constants and key. * * @return self */ public static function from_environment() { $constants = array(); foreach ( self::CONSTANTS as $name ) { if ( defined( $name ) ) { $constants[ $name ] = constant( $name ); } } // The key constant applies whether or not WPMS_ON is set, exactly as in WP Mail SMTP. if ( defined( 'WPMS_CRYPTO_KEY' ) ) { $key = (string) constant( 'WPMS_CRYPTO_KEY' ); } else { $stored = get_option( self::KEY_OPTION ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- WP Mail SMTP stores its binary key base64-encoded. $key = is_string( $stored ) && '' !== $stored ? (string) base64_decode( $stored ) : ''; } return new self( get_option( self::OPTION ), $constants, $key ); } /** * The name the import command takes. * * @return string */ public function id() { return 'wp-mail-smtp'; } /** * The plugin's name. * * @return string */ public function label() { return 'WP Mail SMTP'; } /** * The option read. * * @return string */ public function location() { return self::OPTION; } /** * Whether the option has WP Mail SMTP's shape, or wp-config.php names a mailer for it. * * @return bool */ public function available() { return ( is_array( $this->option ) && isset( $this->option['mail'] ) && is_array( $this->option['mail'] ) ) || $this->constant_set( 'WPMS_MAILER' ); } /** * The mailer WP Mail SMTP is set to. * * @return string */ public function mailer() { if ( ! $this->available() ) { return ''; } return (string) $this->value( 'mail', 'mailer', 'WPMS_MAILER', '' ); } /** * The connection under Adminkeep's field names. autotls and the return path have no * Adminkeep equivalent and are left behind. * * @return array{settings: array, password: string, password_readable: bool} */ public function read() { // WPMS_SSL is honoured whenever it is defined, even empty, which WP Mail SMTP reads as none. $encryption = array_key_exists( 'WPMS_SSL', $this->constants ) ? ( '' === $this->constants['WPMS_SSL'] ? 'none' : $this->constants['WPMS_SSL'] ) : $this->stored( 'smtp', 'encryption', 'none' ); $password = $this->password(); return array( 'settings' => array( 'host' => trim( (string) $this->value( 'smtp', 'host', 'WPMS_SMTP_HOST', '' ) ), 'port' => (int) $this->value( 'smtp', 'port', 'WPMS_SMTP_PORT', 0 ), 'encryption' => in_array( $encryption, array( 'none', 'ssl', 'tls' ), true ) ? $encryption : 'none', // A switch missing from the option reads as off in WP Mail SMTP (its // postprocess_key_defaults()), even though activation saves auth and the forced From // address as on. What it would send is what is copied. 'auth' => (bool) ( array_key_exists( 'WPMS_SMTP_AUTH', $this->constants ) ? $this->constants['WPMS_SMTP_AUTH'] : $this->stored( 'smtp', 'auth', false ) ), 'username' => (string) $this->value( 'smtp', 'user', 'WPMS_SMTP_USER', '' ), 'from_email' => (string) $this->value( 'mail', 'from_email', 'WPMS_MAIL_FROM', '' ), 'from_name' => (string) $this->value( 'mail', 'from_name', 'WPMS_MAIL_FROM_NAME', '' ), 'force_from' => $this->forced( 'from_email_force', 'WPMS_MAIL_FROM_FORCE', false ), 'force_name' => $this->forced( 'from_name_force', 'WPMS_MAIL_FROM_NAME_FORCE', false ), ), 'password' => false === $password ? '' : $password, 'password_readable' => false !== $password, ); } /** * The plain password, '' for none, or false when it cannot be opened. * * WP Mail SMTP's own decrypt() hands back the stored string whenever it fails to open it, so * it would send a sealed blob as the password. Here a failure is a failure: with a key present, * anything that does not open is unreadable. Only with no key at all is a stored value taken * as plain text, which is what a password saved before WP Mail SMTP 1.5 looks like. * * @return string|false */ private function password() { if ( $this->constant_set( 'WPMS_SMTP_PASS' ) ) { return (string) $this->constants['WPMS_SMTP_PASS']; } $stored = $this->stored( 'smtp', 'pass', '' ); $stored = is_string( $stored ) ? $stored : ''; if ( '' === $stored ) { return ''; } if ( '' === $this->key ) { return $stored; } // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Unpacks WP Mail SMTP's sealed value. $raw = base64_decode( $stored, true ); if ( false === $raw || strlen( $raw ) < SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES ) { return false; } try { $plain = sodium_crypto_secretbox_open( substr( $raw, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ), substr( $raw, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ), $this->key ); } catch ( \SodiumException $e ) { // A key of the wrong length: nothing it seals or opens is readable. return false; } // Sealed with the slashes the form posted; WP Mail SMTP strips them after opening. return false === $plain ? false : stripslashes( $plain ); } /** * A field from its constant when WP Mail SMTP would use that constant, else from the option. * WP Mail SMTP ignores an empty constant for these fields. * * @param string $group Option group. * @param string $key Key within the group. * @param string $constant Constant name. * @param mixed $fallback Value when neither has it. * @return mixed */ private function value( $group, $key, $constant, $fallback ) { return $this->constant_set( $constant ) ? $this->constants[ $constant ] : $this->stored( $group, $key, $fallback ); } /** * A force switch. Its constant counts only when it is true or "true", as in WP Mail SMTP. * * @param string $key Key in the mail group. * @param string $constant Constant name. * @param bool $fallback WP Mail SMTP's default. * @return bool */ private function forced( $key, $constant, $fallback ) { if ( isset( $this->constants[ $constant ] ) && in_array( $this->constants[ $constant ], array( true, 'true' ), true ) ) { return true; } return (bool) $this->stored( 'mail', $key, $fallback ); } /** * Whether a constant is in force with a value WP Mail SMTP would use. * * @param string $name Constant name. * @return bool */ private function constant_set( $name ) { return ! empty( $this->constants[ $name ] ); } /** * One key from the option, a string unslashed as WP Mail SMTP unslashes it on reading. A sealed * password is base64, which holds no slashes or quotes, so unslashing it changes nothing. * * @param string $group Option group. * @param string $key Key within the group. * @param mixed $fallback Value when it is missing. * @return mixed */ private function stored( $group, $key, $fallback ) { // isset(), not array_key_exists(): WP Mail SMTP treats a stored null as missing too. if ( ! is_array( $this->option ) || ! isset( $this->option[ $group ] ) || ! is_array( $this->option[ $group ] ) || ! isset( $this->option[ $group ][ $key ] ) ) { return $fallback; } $value = $this->option[ $group ][ $key ]; return is_string( $value ) ? stripslashes( $value ) : $value; } }