Version Description
- Enhancement: New iThemes Sync Verb support for File Change.
- Tweak: Add additional information about the login attempt when calling the Network Brute Force API.
- Bug Fix: Hide Backend Bypass.
- Bug Fix: Strict Standards error during Sync request.
- Bug Fix: wp_die() if a login interstitial session fails to be created instead of throwing a fatal error.
Download this release
Release Info
Developer | chrisjean |
Plugin | iThemes Security (formerly Better WP Security) |
Version | 7.4.1 |
Comparing to | |
See all releases |
Code changes from version 7.4.0 to 7.4.1
- better-wp-security.php +1 -1
- core/core.php +23 -2
- core/history.txt +7 -0
- core/lib.php +19 -1
- core/lib/class-itsec-lib-login-interstitial.php +10 -1
- core/modules/file-change/class-itsec-file-change.php +1 -2
- core/modules/file-change/sync-verbs/itsec-latest-file-scan.php +14 -0
- core/modules/file-change/sync-verbs/itsec-perform-file-scan.php +0 -12
- core/modules/file-change/sync-verbs/itsec-ping-file-scan.php +0 -27
- core/modules/hide-backend/class-itsec-hide-backend.php +18 -2
- core/modules/ipcheck/utilities.php +14 -0
- history.txt +6 -0
- readme.txt +10 -3
better-wp-security.php
CHANGED
@@ -6,7 +6,7 @@
|
|
6 |
* Description: Take the guesswork out of WordPress security. iThemes Security offers 30+ ways to lock down WordPress in an easy-to-use WordPress security plugin.
|
7 |
* Author: iThemes
|
8 |
* Author URI: https://ithemes.com
|
9 |
-
* Version: 7.4.
|
10 |
* Text Domain: better-wp-security
|
11 |
* Network: True
|
12 |
* License: GPLv2
|
6 |
* Description: Take the guesswork out of WordPress security. iThemes Security offers 30+ ways to lock down WordPress in an easy-to-use WordPress security plugin.
|
7 |
* Author: iThemes
|
8 |
* Author URI: https://ithemes.com
|
9 |
+
* Version: 7.4.1
|
10 |
* Text Domain: better-wp-security
|
11 |
* Network: True
|
12 |
* License: GPLv2
|
core/core.php
CHANGED
@@ -49,9 +49,9 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
|
|
49 |
$current_time,
|
50 |
$current_time_gmt,
|
51 |
$is_iwp_call,
|
52 |
-
$request_type,
|
53 |
$wp_upload_dir,
|
54 |
-
$login_interstitial
|
|
|
55 |
|
56 |
|
57 |
/**
|
@@ -900,5 +900,26 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
|
|
900 |
|
901 |
return false;
|
902 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
903 |
}
|
904 |
}
|
49 |
$current_time,
|
50 |
$current_time_gmt,
|
51 |
$is_iwp_call,
|
|
|
52 |
$wp_upload_dir,
|
53 |
+
$login_interstitial,
|
54 |
+
$version;
|
55 |
|
56 |
|
57 |
/**
|
900 |
|
901 |
return false;
|
902 |
}
|
903 |
+
|
904 |
+
/**
|
905 |
+
* Get the actual version string of the plugin.
|
906 |
+
*
|
907 |
+
* This should be used rarely. Any decision flows based off a "version" should be made using the Plugin Build.
|
908 |
+
*
|
909 |
+
* @return string
|
910 |
+
*/
|
911 |
+
public static function get_plugin_version() {
|
912 |
+
$self = self::get_instance();
|
913 |
+
|
914 |
+
if ( ! isset( $self->version ) ) {
|
915 |
+
$data = get_file_data( $self->plugin_file, array(
|
916 |
+
'Version' => 'Version'
|
917 |
+
) );
|
918 |
+
|
919 |
+
$self->version = $data['Version'];
|
920 |
+
}
|
921 |
+
|
922 |
+
return $self->version;
|
923 |
+
}
|
924 |
}
|
925 |
}
|
core/history.txt
CHANGED
@@ -804,3 +804,10 @@
|
|
804 |
5.2.1 - 2019-06-06 - Chris Jean & Timothy Jacobs
|
805 |
Enhancement: Add Security Message when a Notification Center email fails to send.
|
806 |
Enhancement: Replace Trace IP with IP Tracker Online.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
804 |
5.2.1 - 2019-06-06 - Chris Jean & Timothy Jacobs
|
805 |
Enhancement: Add Security Message when a Notification Center email fails to send.
|
806 |
Enhancement: Replace Trace IP with IP Tracker Online.
|
807 |
+
5.2.2 - 2019-06-28 - Chris Jean & Timothy Jacobs
|
808 |
+
Enhancement: New iThemes Sync Verb support for File Change.
|
809 |
+
Tweak: Add additional information about the login attempt when calling the Network Brute Force API.
|
810 |
+
5.2.3 - 2019-08-12 - Chris Jean & Timothy Jacobs
|
811 |
+
Bug Fix: Hide Backend Bypass.
|
812 |
+
Bug Fix: Strict Standards error during Sync request.
|
813 |
+
Bug Fix: wp_die() if a login interstitial session fails to be created instead of throwing a fatal error.
|
core/lib.php
CHANGED
@@ -1065,8 +1065,26 @@ final class ITSEC_Lib {
|
|
1065 |
ITSEC_Modules::set_setting( 'global', 'cron_test_time', $time );
|
1066 |
}
|
1067 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1068 |
public static function fwdslash( $string ) {
|
1069 |
-
return '/' .
|
1070 |
}
|
1071 |
|
1072 |
/**
|
1065 |
ITSEC_Modules::set_setting( 'global', 'cron_test_time', $time );
|
1066 |
}
|
1067 |
|
1068 |
+
/**
|
1069 |
+
* Remove the forward slash.
|
1070 |
+
*
|
1071 |
+
* @param string $string
|
1072 |
+
*
|
1073 |
+
* @return string
|
1074 |
+
*/
|
1075 |
+
public static function unfwdslash( $string ) {
|
1076 |
+
return ltrim( $string, '/' );
|
1077 |
+
}
|
1078 |
+
|
1079 |
+
/**
|
1080 |
+
* Add a forward slash.
|
1081 |
+
*
|
1082 |
+
* @param string $string
|
1083 |
+
*
|
1084 |
+
* @return string
|
1085 |
+
*/
|
1086 |
public static function fwdslash( $string ) {
|
1087 |
+
return '/' . self::unfwdslash( $string );
|
1088 |
}
|
1089 |
|
1090 |
/**
|
core/lib/class-itsec-lib-login-interstitial.php
CHANGED
@@ -160,7 +160,7 @@ class ITSEC_Lib_Login_Interstitial {
|
|
160 |
$session->add_completed_interstitial( $current );
|
161 |
|
162 |
$session->set_current_interstitial( $this->get_next_interstitial( $session ) );
|
163 |
-
$session->save();
|
164 |
}
|
165 |
|
166 |
/**
|
@@ -239,6 +239,10 @@ class ITSEC_Lib_Login_Interstitial {
|
|
239 |
foreach ( $this->get_applicable_interstitials( $user ) as $action => $opts ) {
|
240 |
$session = ITSEC_Login_Interstitial_Session::create( $user, $action );
|
241 |
|
|
|
|
|
|
|
|
|
242 |
$this->build_session_from_global_state( $session );
|
243 |
|
244 |
if ( isset( $_REQUEST[ self::SHOW_AFTER_LOGIN ], $this->registered[ $_REQUEST[ self::SHOW_AFTER_LOGIN ] ] ) ) {
|
@@ -252,6 +256,11 @@ class ITSEC_Lib_Login_Interstitial {
|
|
252 |
|
253 |
if ( isset( $_REQUEST[ self::SHOW_AFTER_LOGIN ], $this->registered[ $_REQUEST[ self::SHOW_AFTER_LOGIN ] ] ) ) {
|
254 |
$session = ITSEC_Login_Interstitial_Session::create( $user, $_REQUEST[ self::SHOW_AFTER_LOGIN ] );
|
|
|
|
|
|
|
|
|
|
|
255 |
$this->build_session_from_global_state( $session );
|
256 |
$session->save();
|
257 |
$this->show_interstitial( $session );
|
160 |
$session->add_completed_interstitial( $current );
|
161 |
|
162 |
$session->set_current_interstitial( $this->get_next_interstitial( $session ) );
|
163 |
+
return $session->save();
|
164 |
}
|
165 |
|
166 |
/**
|
239 |
foreach ( $this->get_applicable_interstitials( $user ) as $action => $opts ) {
|
240 |
$session = ITSEC_Login_Interstitial_Session::create( $user, $action );
|
241 |
|
242 |
+
if ( is_wp_error( $session ) ) {
|
243 |
+
wp_die( $session );
|
244 |
+
}
|
245 |
+
|
246 |
$this->build_session_from_global_state( $session );
|
247 |
|
248 |
if ( isset( $_REQUEST[ self::SHOW_AFTER_LOGIN ], $this->registered[ $_REQUEST[ self::SHOW_AFTER_LOGIN ] ] ) ) {
|
256 |
|
257 |
if ( isset( $_REQUEST[ self::SHOW_AFTER_LOGIN ], $this->registered[ $_REQUEST[ self::SHOW_AFTER_LOGIN ] ] ) ) {
|
258 |
$session = ITSEC_Login_Interstitial_Session::create( $user, $_REQUEST[ self::SHOW_AFTER_LOGIN ] );
|
259 |
+
|
260 |
+
if ( is_wp_error( $session ) ) {
|
261 |
+
wp_die( $session );
|
262 |
+
}
|
263 |
+
|
264 |
$this->build_session_from_global_state( $session );
|
265 |
$session->save();
|
266 |
$this->show_interstitial( $session );
|
core/modules/file-change/class-itsec-file-change.php
CHANGED
@@ -120,8 +120,7 @@ class ITSEC_File_Change {
|
|
120 |
* @param Ithemes_Sync_API $api Sync API object.
|
121 |
*/
|
122 |
public function register_sync_verbs( $api ) {
|
123 |
-
$api->register( 'itsec-
|
124 |
-
$api->register( 'itsec-ping-file-scan', 'Ithemes_Sync_Verb_ITSEC_Ping_File_Scan', dirname( __FILE__ ) . '/sync-verbs/itsec-ping-file-scan.php' );
|
125 |
}
|
126 |
|
127 |
/**
|
120 |
* @param Ithemes_Sync_API $api Sync API object.
|
121 |
*/
|
122 |
public function register_sync_verbs( $api ) {
|
123 |
+
$api->register( 'itsec-latest-file-scan', 'Ithemes_Sync_Verb_ITSEC_Latest_File_Scan', dirname( __FILE__ ) . '/sync-verbs/itsec-latest-file-scan.php' );
|
|
|
124 |
}
|
125 |
|
126 |
/**
|
core/modules/file-change/sync-verbs/itsec-latest-file-scan.php
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class Ithemes_Sync_Verb_ITSEC_Latest_File_Scan extends Ithemes_Sync_Verb {
|
4 |
+
public static $name = 'itsec-latest-file-scan';
|
5 |
+
public static $description = 'Get the latest results from the file scan.';
|
6 |
+
|
7 |
+
public function run( $arguments ) {
|
8 |
+
if ( ! ITSEC_Modules::is_active( 'file-change' ) ) {
|
9 |
+
return new WP_Error( 'itsec-sync-verb-file-change-not-active', __( 'File Change module is not active.', 'better-wp-security' ) );
|
10 |
+
}
|
11 |
+
|
12 |
+
return ITSEC_File_Change::get_latest_changes();
|
13 |
+
}
|
14 |
+
}
|
core/modules/file-change/sync-verbs/itsec-perform-file-scan.php
DELETED
@@ -1,12 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class Ithemes_Sync_Verb_ITSEC_Perform_File_Scan extends Ithemes_Sync_Verb {
|
4 |
-
public static $name = 'itsec-perform-file-scan';
|
5 |
-
public static $description = 'Perform a one-time file scan';
|
6 |
-
|
7 |
-
public function run( $arguments ) {
|
8 |
-
require_once( dirname( dirname( __FILE__ ) ) . '/scanner.php' );
|
9 |
-
|
10 |
-
return ITSEC_File_Change_Scanner::schedule_start();
|
11 |
-
}
|
12 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
core/modules/file-change/sync-verbs/itsec-ping-file-scan.php
DELETED
@@ -1,27 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
/**
|
4 |
-
* Class Ithemes_Sync_Verb_ITSEC_Ping_File_Scan
|
5 |
-
*/
|
6 |
-
class Ithemes_Sync_Verb_ITSEC_Ping_File_Scan extends Ithemes_Sync_Verb {
|
7 |
-
|
8 |
-
public static $name = 'itsec-ping-file-scan';
|
9 |
-
public static $description = 'Ping the file scan for a status update.';
|
10 |
-
|
11 |
-
public function run( $arguments ) {
|
12 |
-
|
13 |
-
require_once( dirname( dirname( __FILE__ ) ) . '/scanner.php' );
|
14 |
-
|
15 |
-
if ( ITSEC_Core::get_scheduler()->is_single_scheduled( 'file-change-fast', null ) ) {
|
16 |
-
ITSEC_Core::get_scheduler()->run_due_now();
|
17 |
-
}
|
18 |
-
|
19 |
-
$status = ITSEC_File_Change_Scanner::get_status();
|
20 |
-
|
21 |
-
if ( ! empty( $status['complete'] ) ) {
|
22 |
-
$status['change_list'] = ITSEC_File_Change::get_latest_changes();
|
23 |
-
}
|
24 |
-
|
25 |
-
return $status;
|
26 |
-
}
|
27 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
core/modules/hide-backend/class-itsec-hide-backend.php
CHANGED
@@ -21,7 +21,6 @@ class ITSEC_Hide_Backend {
|
|
21 |
return;
|
22 |
}
|
23 |
|
24 |
-
|
25 |
add_action( 'itsec_initialized', array( $this, 'handle_specific_page_requests' ), 1000 );
|
26 |
add_action( 'signup_hidden_fields', array( $this, 'add_token_to_registration_form' ) );
|
27 |
add_action( 'login_enqueue_scripts', array( $this, 'login_enqueue' ) );
|
@@ -86,11 +85,28 @@ class ITSEC_Hide_Backend {
|
|
86 |
list( $request_path ) = explode( '/', $request_path );
|
87 |
}
|
88 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
89 |
if ( $request_path === $this->settings['slug'] ) {
|
90 |
$this->handle_login_alias();
|
91 |
} elseif ( in_array( $request_path, array( 'wp-login', 'wp-login.php' ) ) ) {
|
92 |
$this->handle_canonical_login_page();
|
93 |
-
} elseif ( 'wp-admin' === $request_path || 'wp-admin/'
|
94 |
$this->handle_wp_admin_page();
|
95 |
} elseif ( $request_path === $this->settings['register'] && $this->allow_access_to_wp_signup() ) {
|
96 |
$this->handle_registration_alias();
|
21 |
return;
|
22 |
}
|
23 |
|
|
|
24 |
add_action( 'itsec_initialized', array( $this, 'handle_specific_page_requests' ), 1000 );
|
25 |
add_action( 'signup_hidden_fields', array( $this, 'add_token_to_registration_form' ) );
|
26 |
add_action( 'login_enqueue_scripts', array( $this, 'login_enqueue' ) );
|
85 |
list( $request_path ) = explode( '/', $request_path );
|
86 |
}
|
87 |
|
88 |
+
$check = array_unique( array(
|
89 |
+
$request_path,
|
90 |
+
urldecode( $request_path ),
|
91 |
+
ITSEC_Lib::unfwdslash( substr( $_SERVER['SCRIPT_FILENAME'], strlen( ABSPATH ) ) ),
|
92 |
+
) );
|
93 |
+
|
94 |
+
foreach ( $check as $path ) {
|
95 |
+
$this->handle_request_path( $path );
|
96 |
+
}
|
97 |
+
}
|
98 |
+
|
99 |
+
/**
|
100 |
+
* Handle determining if we need to block access to the request path.
|
101 |
+
*
|
102 |
+
* @param string $request_path
|
103 |
+
*/
|
104 |
+
private function handle_request_path( $request_path ) {
|
105 |
if ( $request_path === $this->settings['slug'] ) {
|
106 |
$this->handle_login_alias();
|
107 |
} elseif ( in_array( $request_path, array( 'wp-login', 'wp-login.php' ) ) ) {
|
108 |
$this->handle_canonical_login_page();
|
109 |
+
} elseif ( 'wp-admin' === $request_path || ITSEC_Lib::str_starts_with( $request_path, 'wp-admin/' ) ) {
|
110 |
$this->handle_wp_admin_page();
|
111 |
} elseif ( $request_path === $this->settings['register'] && $this->allow_access_to_wp_signup() ) {
|
112 |
$this->handle_registration_alias();
|
core/modules/ipcheck/utilities.php
CHANGED
@@ -71,6 +71,10 @@ final class ITSEC_Network_Brute_Force_Utilities {
|
|
71 |
'ip' => $ip,
|
72 |
'site' => home_url( '', 'http' ),
|
73 |
'timestamp' => ITSEC_Core::get_current_time_gmt(),
|
|
|
|
|
|
|
|
|
74 |
) );
|
75 |
|
76 |
$response = self::call_api( $action, array(), array(
|
@@ -285,6 +289,16 @@ final class ITSEC_Network_Brute_Force_Utilities {
|
|
285 |
$url = add_query_arg( $query, $url );
|
286 |
}
|
287 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
288 |
$url = apply_filters( 'itsec_ipcheck_api_request_url', $url, $action, $query, $args );
|
289 |
$args = apply_filters( 'itsec_ipcheck_api_request_args', $args, $url, $action, $query );
|
290 |
|
71 |
'ip' => $ip,
|
72 |
'site' => home_url( '', 'http' ),
|
73 |
'timestamp' => ITSEC_Core::get_current_time_gmt(),
|
74 |
+
'login' => array(
|
75 |
+
'details' => ITSEC_Lib::get_login_details(),
|
76 |
+
'agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '',
|
77 |
+
),
|
78 |
) );
|
79 |
|
80 |
$response = self::call_api( $action, array(), array(
|
289 |
$url = add_query_arg( $query, $url );
|
290 |
}
|
291 |
|
292 |
+
if ( ! isset( $args['user-agent'] ) ) {
|
293 |
+
if ( ITSEC_Core::is_pro() ) {
|
294 |
+
$args['user-agent'] = 'iThemes Security Pro/';
|
295 |
+
} else {
|
296 |
+
$args['user-agent'] = 'iThemes Security/';
|
297 |
+
}
|
298 |
+
|
299 |
+
$args['user-agent'] .= ITSEC_Core::get_plugin_version() . '-' . ITSEC_Core::get_plugin_build() . '; WordPress/' . get_bloginfo( 'version' );
|
300 |
+
}
|
301 |
+
|
302 |
$url = apply_filters( 'itsec_ipcheck_api_request_url', $url, $action, $query, $args );
|
303 |
$args = apply_filters( 'itsec_ipcheck_api_request_args', $args, $url, $action, $query );
|
304 |
|
history.txt
CHANGED
@@ -841,3 +841,9 @@
|
|
841 |
Enhancement: Add Security Message when a Notification Center email fails to send.
|
842 |
Enhancement: Replace Trace IP with IP Tracker Online.
|
843 |
Tweak: Remove 'DELETE' method from "System Tweaks -> Filter Request Methods"
|
|
|
|
|
|
|
|
|
|
|
|
841 |
Enhancement: Add Security Message when a Notification Center email fails to send.
|
842 |
Enhancement: Replace Trace IP with IP Tracker Online.
|
843 |
Tweak: Remove 'DELETE' method from "System Tweaks -> Filter Request Methods"
|
844 |
+
7.4.1 - 2019-08-15 - Chris Jean & Timothy Jacobs
|
845 |
+
Enhancement: New iThemes Sync Verb support for File Change.
|
846 |
+
Tweak: Add additional information about the login attempt when calling the Network Brute Force API.
|
847 |
+
Bug Fix: Hide Backend Bypass.
|
848 |
+
Bug Fix: Strict Standards error during Sync request.
|
849 |
+
Bug Fix: wp_die() if a login interstitial session fails to be created instead of throwing a fatal error.
|
readme.txt
CHANGED
@@ -3,7 +3,7 @@ Contributors: ithemes, chrisjean, mattdanner, timothyblynjacobs
|
|
3 |
Tags: security, security plugin, malware, hack, secure, block, SSL, admin, htaccess, lockdown, login, protect, protection, anti virus, attack, injection, login security, maintenance, permissions, prevention, authentication, administration, password, brute force, ban, permissions, bots, user agents, xml rpc, security log
|
4 |
Requires at least: 4.7
|
5 |
Tested up to: 5.2.2
|
6 |
-
Stable tag: 7.4.
|
7 |
Requires PHP: 5.2
|
8 |
License: GPLv2 or later
|
9 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
@@ -189,6 +189,13 @@ Free support may be available with the help of the community in the <a href="htt
|
|
189 |
|
190 |
== Changelog ==
|
191 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
192 |
= 7.4.0 =
|
193 |
* New: iThemes Security Admin Notices are now conveniently located in the new Security Messages Menu. Check your notices in the Security menu on the WordPress Admin Bar.
|
194 |
* Enhancement: Add Security Message when a Notification Center email fails to send.
|
@@ -540,5 +547,5 @@ Free support may be available with the help of the community in the <a href="htt
|
|
540 |
|
541 |
== Upgrade Notice ==
|
542 |
|
543 |
-
= 7.4.
|
544 |
-
Version 7.4.
|
3 |
Tags: security, security plugin, malware, hack, secure, block, SSL, admin, htaccess, lockdown, login, protect, protection, anti virus, attack, injection, login security, maintenance, permissions, prevention, authentication, administration, password, brute force, ban, permissions, bots, user agents, xml rpc, security log
|
4 |
Requires at least: 4.7
|
5 |
Tested up to: 5.2.2
|
6 |
+
Stable tag: 7.4.1
|
7 |
Requires PHP: 5.2
|
8 |
License: GPLv2 or later
|
9 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
189 |
|
190 |
== Changelog ==
|
191 |
|
192 |
+
= 7.4.1 =
|
193 |
+
* Enhancement: New iThemes Sync Verb support for File Change.
|
194 |
+
* Tweak: Add additional information about the login attempt when calling the Network Brute Force API.
|
195 |
+
* Bug Fix: Hide Backend Bypass.
|
196 |
+
* Bug Fix: Strict Standards error during Sync request.
|
197 |
+
* Bug Fix: wp_die() if a login interstitial session fails to be created instead of throwing a fatal error.
|
198 |
+
|
199 |
= 7.4.0 =
|
200 |
* New: iThemes Security Admin Notices are now conveniently located in the new Security Messages Menu. Check your notices in the Security menu on the WordPress Admin Bar.
|
201 |
* Enhancement: Add Security Message when a Notification Center email fails to send.
|
547 |
|
548 |
== Upgrade Notice ==
|
549 |
|
550 |
+
= 7.4.1 =
|
551 |
+
Version 7.4.1 contains important improvements. It is recommended for all users.
|